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_lr1110.cpp

Committer:
dudmuck
Date:
2021-08-24
Revision:
13:8ce61a1897ab
Parent:
10:db4e11a55bda

File content as of revision 13:8ce61a1897ab:

#include "radio.h"
#ifdef SX1265_H 

SPI spi(D11, D12, D13); // mosi, miso, sclk
                   //spi, nss, busy, dio9, nreset
SX1265 Radio::radio(spi, D7, D3, D5, A0, 0x3ffffcf);

#define DIO_en_IDX        0
#define DIO_stby_IDX      1
#define DIO_rx_IDX        2
#define DIO_tx_IDX        3
#define DIO_txhp_IDX      4
#define DIO_gnss_IDX      6
#define DIO_wifi_IDX      7

#define DIO5_BIT         0x01
#define DIO6_BIT         0x02
#define DIO7_BIT         0x04
#define DIO8_BIT         0x08
#define DIO10_BIT        0x10

uint8_t dioBuf[8];
uint8_t Radio::gfsk_pp_buf[9];
uint8_t Radio::gfsk_mp_buf[10];
uint8_t Radio::lora_pp_buf[6];
uint8_t Radio::lora_mp_buf[4];
const RadioEvents_t* Radio::RadioEvents;
uint8_t Radio::pktType;
uint8_t Radio::tx_param_buf[2];
uint8_t Radio::pa_config_buf[4];
unsigned Radio::recalCnt;
uint32_t gfsk_crc_initValue;
uint32_t gfsk_crc_Poly;

uint8_t wifiScan_buf[9];
uint8_t gnssAutonomous_buf[9];
uint8_t gnssAssisted_buf[7];
bool wifiResultFormatBasic;
uint8_t tcxo_buf[5];

void Radio::hw_reset()
{
    radio.hw_reset();
    //radio.enable_default_irqs();
    initRfSwDIO();
}

void Radio::initRfSwDIO()
{
/* antenna truth table
 * V1    V2    port
 *  0     0    shutdown
 *  1     0    J2   rx RFI
 *  0     1    J1   HP tx RFO
 *  1     1    J3   LP tx RFO
 * DIO5  DIO6
 * */
    dioBuf[  DIO_en_IDX] = DIO5_BIT | DIO6_BIT;
    dioBuf[DIO_stby_IDX] = 0;
    dioBuf[  DIO_rx_IDX] = DIO5_BIT;
    dioBuf[  DIO_tx_IDX] = DIO5_BIT | DIO6_BIT;
    dioBuf[DIO_txhp_IDX] = DIO6_BIT;
    dioBuf[DIO_gnss_IDX] = 0;
    dioBuf[DIO_wifi_IDX] = 0;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
}

unsigned Radio::my_round(float x)
{
    if (x >= 0)
        return (unsigned) (x+0.5);
    return (unsigned) (x-0.5);
}

void Radio::readChip()
{
    uint32_t u32;
    {
        txParamsB_t txpb;   // txpb.bits.PaSel
        txParamsC_t txpc;
        radio.memRegRead(REG_ADDR_TX_PARAMS_C, 1, &txpc.dword);
        tx_param_buf[1] = txpc.bits.pa_ramp_time;

        radio.memRegRead(REG_ADDR_TX_PARAMS_B, 1, &txpb.dword);
        if (txpb.bits.PaSel)
            tx_param_buf[0] = txpc.bits.tx_dbm - 9;
        else
            tx_param_buf[0] = txpc.bits.tx_dbm - 17;
    }

    radio.GetPaConfig(pa_config_buf);

    {
        dioEnable_t dio;
        radio.memRegRead(REG_ADDR_DIO10, 1, &dio.dword);
        if (dio.bits.enable)
            dioBuf[DIO_en_IDX] |= DIO10_BIT;
        else
            dioBuf[DIO_en_IDX] &= ~DIO10_BIT;

        radio.memRegRead(REG_ADDR_DIO8, 1, &dio.dword);
        if (dio.bits.enable)
            dioBuf[DIO_en_IDX] |= DIO8_BIT;
        else
            dioBuf[DIO_en_IDX] &= ~DIO8_BIT;

        radio.memRegRead(REG_ADDR_DIO7, 1, &dio.dword);
        if (dio.bits.enable)
            dioBuf[DIO_en_IDX] |= DIO7_BIT;
        else
            dioBuf[DIO_en_IDX] &= ~DIO7_BIT;

        radio.memRegRead(REG_ADDR_DIO6, 1, &dio.dword);
        if (dio.bits.enable)
            dioBuf[DIO_en_IDX] |= DIO6_BIT;
        else
            dioBuf[DIO_en_IDX] &= ~DIO6_BIT;

        radio.memRegRead(REG_ADDR_DIO5, 1, &dio.dword);
        if (dio.bits.enable)
            dioBuf[DIO_en_IDX] |= DIO5_BIT;
        else
            dioBuf[DIO_en_IDX] &= ~DIO5_BIT;
    }

    {
        gfskConfig4_t cfg4;
        gfskConfig2_t cfg2;
        gfskConfig1_t cfg1;
        gfskConfig3_t cfg3;
        gfskConfig5_t cfg5;

        radio.memRegRead(REG_ADDR_GFSK_CFG5, 1, &cfg5.dword);
        gfsk_pp_buf[8] = cfg5.bits.whitening_enable;

        if (cfg5.bits.crc_off)
            gfsk_pp_buf[7] |= 1;
        else
            gfsk_pp_buf[7] &= ~1;

        if (cfg5.bits.crc_size)
            gfsk_pp_buf[7] |= 2;
        else
            gfsk_pp_buf[7] &= ~2;

        if (cfg5.bits.crc_invert)
            gfsk_pp_buf[7] |= 4;
        else
            gfsk_pp_buf[7] &= ~4;

        radio.memRegRead(REG_ADDR_GFSK_PAYLOAD_LENGTH_A, 1, &u32);
        gfsk_pp_buf[6] = u32 & 0xff;

        radio.memRegRead(REG_ADDR_GFSK_PAYLOAD_LENGTH_B, 1, &cfg4.dword);
        gfsk_pp_buf[4] = cfg4.bits.addr_comp;
        if (cfg4.bits.payload_length != gfsk_pp_buf[6])
            log_printf("length_mismatch_%02x_%02x\r\n", cfg4.bits.payload_length, gfsk_pp_buf[6]);

        radio.memRegRead(REG_ADDR_GFSK_CFG3, 1, &cfg3.dword);
        gfsk_pp_buf[5] = cfg3.bits.variable_length;

        radio.memRegRead(REG_ADDR_GFSK_CFG2, 1, &cfg2.dword);
        gfsk_pp_buf[3] = cfg2.bits.sync_word_length;

        radio.memRegRead(REG_ADDR_GFSK_CFG1, 1, &cfg1.dword);
        radio.to_big_endian16(cfg1.bits.preamble_length, gfsk_pp_buf);
        if (cfg1.bits.preamble_det_enable)
            gfsk_pp_buf[2] = cfg1.bits.preamble_det_len;
        else
            gfsk_pp_buf[2] = 0;

        {
            unsigned hz;
            radio.memRegRead(REG_ADDR_GFSK_BITRATE, 1, &u32);
            hz = GFSK_BITRATE_NUMERATOR / u32;
            radio.to_big_endian32(hz, gfsk_mp_buf);
        }
        //gfsk_mp_buf[4] = bt;
        //gfsk_mp_buf[5] = bwf;
        //gfsk_mp_buf[6,7,8,9] = fdevHz;
        {
            unsigned hz;
            radio.memRegRead(REG_ADDR_GFSK_FDEV, 1, &u32);
            hz = my_round(u32 * FREQ_STEP);
            radio.to_big_endian32(hz, gfsk_mp_buf+6);
        }

        radio.memRegRead(REG_ADDR_GFSK_CRC_INIT, 1, &gfsk_crc_initValue);
        radio.memRegRead(REG_ADDR_GFSK_CRC_POLY, 1, &gfsk_crc_Poly);
    }

    /**********************************************/
    {
        uint16_t u16;
        loraConfig0_t cfg0;
        loraConfigB_t cfgb;
        loraConfigC_t cfgc;

        radio.GetLoRaModulationParameters(lora_mp_buf);

        radio.memRegRead(REG_ADDR_LORA_CONFIGC, 1, &cfgc.dword);
        u16 = cfgc.bits.preamble_length;
        lora_pp_buf[1] = u16 & 0xff;
        u16 >>= 8;
        lora_pp_buf[0] = u16;

        radio.memRegRead(REG_ADDR_LORA_CONFIG0, 1, &cfg0.dword);
        lora_pp_buf[2] = cfg0.bits.implicit_header;
        lora_pp_buf[3] = cfg0.bits.payload_length;
        lora_pp_buf[4] = cfg0.bits.crc_on;

        radio.memRegRead(REG_ADDR_LORA_CONFIGB, 1, &cfgb.dword);
        lora_pp_buf[5] = cfgb.bits.invertIQ;

    }

    {   /* wifi scan defaults */
        unsigned chanmask = 0x0421;
        unsigned timeout = 0x0046;

        wifiScan_buf[0] = 0x01; // wifi type
        wifiScan_buf[2] = chanmask; // chanmask-lo
        chanmask >>= 8;
        wifiScan_buf[1] = chanmask; // chanmask-hi
        wifiScan_buf[3] = 0x02; // acqMode
        wifiScan_buf[4] = 0x0a; // NbMaxRes
        wifiScan_buf[5] = 0x06; // NbScanPerChan
        wifiScan_buf[7] = timeout; // Timeout-lo
        timeout >>= 8;
        wifiScan_buf[6] = timeout; // Timeout-hi
        wifiScan_buf[8] = 0x00; // AbortOnTimeout
    }

}

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

uint8_t Radio::get_payload_length()
{
    uint8_t pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_LORA) {
        unsigned foo;
        loraConfig0_t cfg0;
        radio.memRegRead(REG_ADDR_LORA_CONFIG0, 1, &cfg0.dword);
        foo = cfg0.dword;
        foo >>= 24;
        lora_pp_buf[3] = cfg0.bits.payload_length;
        return cfg0.bits.payload_length;
    } else if (pktType == PACKET_TYPE_GFSK) {
        uint32_t u32;
        /* TODO: which is rx payloadLength vs tx payloadLength */
        //gfskConfig4_t cfg4;
        radio.memRegRead(REG_ADDR_GFSK_PAYLOAD_LENGTH_A, 1, &u32);
        gfsk_pp_buf[6] = u32 & 0xff;
        /*radio.memRegRead(REG_ADDR_GFSK_PAYLOAD_LENGTH_B, 1, buf);
        cfg4.dword = radio.from_big_endian32(buf);*/

        /*
        if ((rb & 0xff) != cfg4.bits.payload_length) {
            log_printf("gfsk payload length A:%u B:%u\r\n", rb & 0xff, cfg4.bits.payload_length);
            // todo: which is tx-length vs rx-length
        }
        */

        return u32 & 0xff;
    }
    return 0;
}

bool Radio::tx_payload_length_write(const char* txt)
{
    unsigned len;
    sscanf(txt, "%u", &len);
    set_payload_length(len);
    return false;
}

unsigned Radio::read_register(unsigned addr)
{
    uint32_t u32;
    radio.memRegRead(addr, 1, &u32);
    return u32;
}

void Radio::write_register(unsigned addr, unsigned val)
{
    stat_t stat;
    uint8_t buf[8];
    radio.to_big_endian32(addr, buf);
    radio.to_big_endian32(val, buf);
    stat.word = radio.xfer(OPCODE_WRITEREGMEM32, 8, 0, buf);
    print_stat(stat);
}

void Radio::txTimeout_print()
{
    float sec = radio.txTimeout / 32768.0;
    printf("%.3f", sec);
}

bool Radio::txTimeout_write(const char *txt)
{
    float sec;
    if (sscanf(txt, "%f", &sec) == 1) {
        radio.txTimeout = sec * 32768;
        log_printf("txTimeout:%u\r\n", radio.txTimeout);
    }
    return false;
}

const value_item_t Radio::txTimeout_item = { _ITEM_VALUE, 6, txTimeout_print, txTimeout_write};

bool regulator_read()
{
    regulatorMode_t rm;
    Radio::radio.memRegRead(REG_ADDR_REGULATOR_MODE, 1, &rm.dword);
    return rm.bits.dcdc_en;
}

bool regulator_push()
{
    uint8_t buf = regulator_read() ? 0 : 1;
    Radio::radio.xfer(OPCODE_SET_REGULATOR_MODE, 1, 0, &buf);
    return buf == 1;
}

const toggle_item_t regulator_item = { _ITEM_TOGGLE,
    " LDO ",
    "DC-DC",
    regulator_read,
    regulator_push
};

const char* const tcxovolts_strs[] 
{
    "1.6", // 0
    "1.8", // 1
    "1.8", // 2
    "2.2", // 3
    "2.4", // 4
    "2.7", // 5
    "3.0", // 6
    "3.3", // 7
    NULL
};

unsigned tcxovolts_read(bool for_writing)
{
    tcxo_t tcxo;
    Radio::radio.memRegRead(REG_ADDR_TCXO, 1, &tcxo.dword);
    return tcxo.bits.volts;
}

menuMode_e tcxovolts_write(unsigned sidx)
{
    tcxo_buf[0] = sidx;
    Radio::radio.xfer(OPCODE_SET_TCXO_MODE, 5, 0, tcxo_buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t tcxo_volts_item = { _ITEM_DROPDOWN, tcxovolts_strs, tcxovolts_strs, tcxovolts_read, tcxovolts_write};

void tcxo_delay_print()
{
    unsigned ticks = tcxo_buf[1];
    ticks <<= 8;
    ticks |= tcxo_buf[2];
    ticks <<= 8;
    ticks |= tcxo_buf[3];
    printf("%.1f", ticks / 32.768);
}

bool tcxo_delay_write(const char *txt)
{
    float ms;
    if (sscanf(txt, "%f", &ms) == 1) {
        unsigned ticks = ms * 32.768;
        Radio::radio.to_big_endian24(ticks, tcxo_buf+1);
        Radio::radio.xfer(OPCODE_SET_TCXO_MODE, 5, 0, tcxo_buf);
    }
    return false;
}

const value_item_t tcxo_delay_item = { _ITEM_VALUE, 5, tcxo_delay_print, tcxo_delay_write};

void recalibrate_print() { }

bool recalibrate_write(const char *txt)
{
    unsigned bits;
    uint8_t buf = 0;
    if (sscanf(txt, "%x", &bits) == 1) {
        buf = bits;
    }
    Radio::radio.xfer(OPCODE_CALIBRATE, 1, 0, &buf);
    return false;
}

const value_item_t recalibrate_item = { _ITEM_VALUE, 5, recalibrate_print, recalibrate_write};


void Radio::rxBuffer_push()
{
    uint8_t buf[4];
    stat_t stat;
    stat.word = radio.xfer(OPCODE_GET_RX_BUFFER_STATUS, 0, 0, NULL);
    stat.word = radio.xfer(0x0000, 0, 2, buf);
    log_printf("rxBufferStatus:%s\r\n", radio.cmdStatus_toString(stat.bits.cmdStatus));
    if (stat.bits.cmdStatus == CMD_DAT) {
        uint8_t len = buf[0];
        uint8_t offset = buf[1];
        buf[0] = offset;
        buf[1] = len;
        stat.word = radio.xfer(OPCODE_READ_BUFFER8, 2, 0, NULL);
        log_printf("len%u ofs:%u readBuffer8cmd:%s\r\n", len, offset, radio.cmdStatus_toString(stat.bits.cmdStatus));
        stat.word = radio.xfer(0x0000, 0, len, radio.rx_buf);
        log_printf("readBuffer8resp:%s\r\n", radio.cmdStatus_toString(stat.bits.cmdStatus));
        printRxPkt(len);
    }
}

const button_item_t Radio::rxBufferStatus_item = { _ITEM_BUTTON, "rxbufStat", rxBuffer_push};

void Radio::getStats_push(void)
{
    uint8_t buf[8];
    stat_t stat;
    stat.word = radio.xfer(OPCODE_GET_STATS, 0, 0, NULL);
    stat.word = radio.xfer(0x0000, 0, 8, buf);
    if (stat.bits.cmdStatus == CMD_DAT) {
        uint8_t pktType = radio.getPacketType();
        uint16_t data1, data2, nbPktCrcErr, nbPktRx = buf[0];
        nbPktRx <<= 8;
        nbPktRx |= buf[1];
        nbPktCrcErr = buf[2];
        nbPktCrcErr <<= 8;
        nbPktCrcErr |= buf[3];
        data1 = buf[4];
        data1 <<= 8;
        data1 |= buf[5];
        data2 = buf[6];
        data2 <<= 8;
        data2 |= buf[7];
        if (pktType == PACKET_TYPE_LORA) {
            log_printf("nbPktRx:%u nbPktCrcErr:%u hdrErr:%u falseSync:%u\r\n", nbPktRx, nbPktCrcErr, data1, data2);
        } else if (pktType == PACKET_TYPE_GFSK) {
            log_printf("nbPktRx:%u nbPktCrcErr:%u nbLenErr:%u\r\n", nbPktRx, nbPktCrcErr, data1);
        }
    }
}

const button_item_t Radio::getStats_item = { _ITEM_BUTTON, "getStats", getStats_push};


void Radio::txPkt()
{
    uint8_t txlen = get_payload_length();
    radio.start_tx(txlen);
}

void Radio::Rx()
{
    stat_t stat;
    uint8_t buf[3];
    unsigned rx_timeout = 0xffffff; // receive until instructed not to
    radio.to_big_endian24(rx_timeout, buf);
    stat.word = radio.xfer(OPCODE_SET_RX, 3, 0, buf);
    print_stat(stat);
}

void Radio::tx_carrier()
{
    stat_t stat;
    stat.word = radio.xfer(OPCODE_SET_TXCW, 0, 0, NULL);
    print_stat(stat);
}

void Radio::tx_preamble()
{
    stat_t stat;
    stat.word = radio.xfer(OPCODE_SET_TX_PREAMBLE, 0, 0, NULL);
    print_stat(stat);
}

void Radio::print_stat(stat_t stat)
{
    char out[96];
    char str[24];
    out[0] = 0;
    if (stat.bits.rfu)
        strcat(out, "\e[41m");
    if ((stat.word & 0xff) != 0xff) {   // xfer returns 0xff for stat2 if stat2 didnt exist
        if (stat.bits.bootloader)
            strcat(out, "flash ");
        else
            strcat(out, "bootloader ");
        switch (stat.bits.chipMode) {
            case 0: strcat(out, "sleep"); break;
            case 1: strcat(out, "stby-rc"); break;
            case 2: strcat(out, "stby-xtal"); break;
            case 3: strcat(out, "\e[33mfs"); break;
            case 4: strcat(out, "\e[32mrx"); break;
            case 5: strcat(out, "\e[31mtx"); break;
            case 6: strcat(out, "\e[36mwifi/gnss"); break;
            default:
                sprintf(str, "\e[31mchipmode:%u", stat.bits.chipMode);
                strcat(out, str);
                break;
        }
        strcat(out, "\e[0m ");
        if (stat.bits.resetStatus) {
            strcat(out, "reset:");
            switch (stat.bits.resetStatus) {
                case 1: strcat(out, "analog"); break;
                case 2: strcat(out, "pin"); break;
                case 3: strcat(out, "system"); break;
                case 4: strcat(out, "watchdog"); break;
                case 5: strcat(out, "iocd"); break;
                case 6: strcat(out, "rtc"); break;
                default:
                    sprintf(str, "<%u>", stat.bits.resetStatus);
                    strcat(out, str);
                    break;
            }
            strcat(out, " ");
        }
    } // ..if stat2 exists

    sprintf(str, " %s ", radio.cmdStatus_toString(stat.bits.cmdStatus));
    strcat(out, str);
    if (stat.bits.intActive)
        strcat(out, "intActive ");
    if (stat.bits.rfu)
        strcat(out, "\e[0m");

    log_printf("%s\r\n", out);
}


void Radio::set_payload_length(uint8_t len)
{
    uint8_t *buf_ptr = NULL;
    uint8_t buf_len = 0;
    uint8_t pktType = radio.getPacketType();
    if (pktType == PACKET_TYPE_LORA) {
        buf_len = 6;
        buf_ptr = lora_pp_buf;
        lora_pp_buf[3] = len;
    } else if (pktType == PACKET_TYPE_GFSK) {
        buf_len = 9;
        buf_ptr = gfsk_pp_buf;
        gfsk_pp_buf[6] = len;
    }
    radio.xfer(OPCODE_SET_PACKET_PARAM, buf_len, 0, buf_ptr);
}

bool Radio::PaSel_read()
{
    txParamsB_t txpb;
    radio.memRegRead(REG_ADDR_TX_PARAMS_B, 1, &txpb.dword);
    pa_config_buf[0] = txpb.bits.PaSel;
    return txpb.bits.PaSel;
}

bool Radio::PaSel_push()
{
    pa_config_buf[0] ^= 1;
    radio.xfer(OPCODE_SET_PA_CONFIG, 4, 0, pa_config_buf);
    return pa_config_buf[0] == 1;
}

const toggle_item_t Radio::PaSel_item = { _ITEM_TOGGLE, "PaSel:LP", "PaSel:HP", PaSel_read, PaSel_push};

bool Radio::RegPASupply_read()
{
    txParamsA_t tpa;
    radio.memRegRead(REG_ADDR_TX_PARAMS_A, 1, &tpa.dword);
    pa_config_buf[1] = tpa.bits.RegPASupply;
    return tpa.bits.RegPASupply;
}

bool Radio::RegPASupply_push(void)
{
    pa_config_buf[1] ^= 1;
    radio.xfer(OPCODE_SET_PA_CONFIG, 4, 0, pa_config_buf);
    return pa_config_buf[1] == 1;
}

const toggle_item_t Radio::RegPASupply_item = { _ITEM_TOGGLE,
    "PASupply:intreg",
    "PASupply:vbat  ",
    RegPASupply_read,
    RegPASupply_push
};

void Radio::PaDutyCycle_print()
{
    txParamsB_t tpb;
    radio.memRegRead(REG_ADDR_TX_PARAMS_B, 1, &tpb.dword);
    pa_config_buf[2] = tpb.bits.PaDutyCycle;
    printf("%u", tpb.bits.PaDutyCycle);
}

bool Radio::PaDutyCycle_write(const char *txt)
{
    unsigned n;
    if (sscanf(txt, "%u", &n) == 1) {
        pa_config_buf[2] = n;
        radio.xfer(OPCODE_SET_PA_CONFIG, 4, 0, pa_config_buf);
    }
    return false;
}

const value_item_t Radio::PaDutyCycle_item = { _ITEM_VALUE, 3, PaDutyCycle_print, PaDutyCycle_write};

void Radio::PaHPSel_print()
{
    txParamsB_t tpb;
    radio.memRegRead(REG_ADDR_TX_PARAMS_B, 1, &tpb.dword);
    pa_config_buf[3] = tpb.bits.PaHPSel;
    printf("%u", tpb.bits.PaHPSel);
}

bool Radio::PaHPSel_write(const char *txt)
{
    unsigned n;
    if (sscanf(txt, "%u", &n) == 1) {
        pa_config_buf[3] = n;
        radio.xfer(OPCODE_SET_PA_CONFIG, 4, 0, pa_config_buf);
    }
    return false;
}

const value_item_t Radio::PaHPSel_item = { _ITEM_VALUE, 3, PaHPSel_print, PaHPSel_write};


void Radio::wifiScan_push()
{
    radio.xfer(OPCODE_WIFI_SCAN, 9, 0, wifiScan_buf);
    log_printf("wifiScan...\r\n");
}

const char* const wifitype_strs[] = 
{
    "802.11b", // 1
    "802.11g", // 2
    "802.11n", // 3
    "  all  ", // 4
    NULL
};

unsigned wifitype_read(bool for_writing)
{
    return wifiScan_buf[0] - 1;
}

menuMode_e wifitype_write(unsigned sidx)
{
    wifiScan_buf[0] = sidx + 1;
    return MENUMODE_REDRAW;
}

const dropdown_item_t wifiType_item = { _ITEM_DROPDOWN, wifitype_strs, wifitype_strs, wifitype_read, wifitype_write };

bool wifiAcqMode_read()
{
    return wifiScan_buf[3] == 2;
}

bool wifiAcqMode_push()
{
    wifiScan_buf[3] == 2 ?  wifiScan_buf[3] = 1 : wifiScan_buf[3] = 2;
    return wifiScan_buf[3] == 2;
}

const toggle_item_t wifiAcqMode_item = { _ITEM_TOGGLE,
    "beacon-only  ",
    "beacon+Packet",
    wifiAcqMode_read,
    wifiAcqMode_push
};

void wifiNbMaxRes_print()
{
    printf("%u", wifiScan_buf[4]);
}

bool wifiNbMaxRes_write(const char *txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    wifiScan_buf[4] = n;
    return false;
}

const value_item_t wifiNbMaxRes_item = { _ITEM_VALUE, 3, wifiNbMaxRes_print, wifiNbMaxRes_write};

void wifiNbScanPerChan_print()
{
    printf("%u", wifiScan_buf[5]);
}

bool wifiNbScanPerChan_write(const char *txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    wifiScan_buf[5] = n;
    return false;
}

const value_item_t wifiNbScanPerChan_item = { _ITEM_VALUE, 3, wifiNbScanPerChan_print, wifiNbScanPerChan_write};



const button_item_t Radio::wifiScan_item = { _ITEM_BUTTON, "wifiScan", wifiScan_push };

bool wifi_ch14_read() { return (wifiScan_buf[1] & 0x20) == 0x20; }
bool wifi_ch14_push() { wifiScan_buf[1] ^= 0x20; return wifi_ch14_read(); }
const toggle_item_t wifi_ch14_item = { _ITEM_TOGGLE, "ch14", NULL, wifi_ch14_read, wifi_ch14_push};

bool wifi_ch13_read() { return (wifiScan_buf[1] & 0x10) == 0x10; }
bool wifi_ch13_push() { wifiScan_buf[1] ^= 0x10; return wifi_ch13_read(); }
const toggle_item_t wifi_ch13_item = { _ITEM_TOGGLE, "ch13", NULL, wifi_ch13_read, wifi_ch13_push};

bool wifi_ch12_read() { return (wifiScan_buf[1] & 0x08) == 0x08; }
bool wifi_ch12_push() { wifiScan_buf[1] ^= 0x08; return wifi_ch12_read(); }
const toggle_item_t wifi_ch12_item = { _ITEM_TOGGLE, "ch12", NULL, wifi_ch12_read, wifi_ch12_push};

bool wifi_ch11_read() { return (wifiScan_buf[1] & 0x04) == 0x04; }
bool wifi_ch11_push() { wifiScan_buf[1] ^= 0x08; return wifi_ch11_read(); }
const toggle_item_t wifi_ch11_item = { _ITEM_TOGGLE, "ch11", NULL, wifi_ch11_read, wifi_ch11_push};

bool wifi_ch10_read() { return (wifiScan_buf[1] & 0x02) == 0x02; }
bool wifi_ch10_push() { wifiScan_buf[1] ^= 0x02; return wifi_ch10_read(); }
const toggle_item_t wifi_ch10_item = { _ITEM_TOGGLE, "ch10", NULL, wifi_ch10_read, wifi_ch10_push};

bool wifi_ch9_read() { return (wifiScan_buf[1] & 0x01) == 0x01; }
bool wifi_ch9_push() { wifiScan_buf[1] ^= 0x01; return wifi_ch9_read(); }
const toggle_item_t wifi_ch9_item = { _ITEM_TOGGLE, "ch9", NULL, wifi_ch9_read, wifi_ch9_push};

bool wifi_ch8_read() { return (wifiScan_buf[2] & 0x80) == 0x80; }
bool wifi_ch8_push() { wifiScan_buf[2] ^= 0x80; return wifi_ch8_read(); }
const toggle_item_t wifi_ch8_item = { _ITEM_TOGGLE, "ch8", NULL, wifi_ch8_read, wifi_ch8_push};

bool wifi_ch7_read() { return (wifiScan_buf[2] & 0x40) == 0x40; }
bool wifi_ch7_push() { wifiScan_buf[2] ^= 0x40; return wifi_ch7_read(); }
const toggle_item_t wifi_ch7_item = { _ITEM_TOGGLE, "ch7", NULL, wifi_ch7_read, wifi_ch7_push};

bool wifi_ch6_read() { return (wifiScan_buf[2] & 0x20) == 0x20; }
bool wifi_ch6_push() { wifiScan_buf[2] ^= 0x20; return wifi_ch6_read(); }
const toggle_item_t wifi_ch6_item = { _ITEM_TOGGLE, "ch6", NULL, wifi_ch6_read, wifi_ch6_push};

bool wifi_ch5_read() { return (wifiScan_buf[2] & 0x10) == 0x10; }
bool wifi_ch5_push() { wifiScan_buf[2] ^= 0x10; return wifi_ch5_read(); }
const toggle_item_t wifi_ch5_item = { _ITEM_TOGGLE, "ch5", NULL, wifi_ch5_read, wifi_ch5_push};

bool wifi_ch4_read() { return (wifiScan_buf[2] & 0x08) == 0x08; }
bool wifi_ch4_push() { wifiScan_buf[2] ^= 0x08; return wifi_ch4_read(); }
const toggle_item_t wifi_ch4_item = { _ITEM_TOGGLE, "ch4", NULL, wifi_ch4_read, wifi_ch4_push};

bool wifi_ch3_read() { return (wifiScan_buf[2] & 0x04) == 0x04; }
bool wifi_ch3_push() { wifiScan_buf[2] ^= 0x04; return wifi_ch3_read(); }
const toggle_item_t wifi_ch3_item = { _ITEM_TOGGLE, "ch3", NULL, wifi_ch3_read, wifi_ch3_push};

bool wifi_ch2_read() { return (wifiScan_buf[2] & 0x02) == 0x02; }
bool wifi_ch2_push() { wifiScan_buf[2] ^= 0x02; return wifi_ch2_read(); }
const toggle_item_t wifi_ch2_item = { _ITEM_TOGGLE, "ch2", NULL, wifi_ch2_read, wifi_ch2_push};

bool wifi_ch1_read() { return (wifiScan_buf[2] & 0x01) == 0x01; }
bool wifi_ch1_push() { wifiScan_buf[2] ^= 0x01; return wifi_ch1_read(); }
const toggle_item_t wifi_ch1_item = { _ITEM_TOGGLE, "ch1", NULL, wifi_ch1_read, wifi_ch1_push};

void wifiTimeout_print(void)
{
    unsigned t;
    t = wifiScan_buf[6];
    t <<= 8;
    t |= wifiScan_buf[7];
    printf("%u", t);
}

bool wifiTimeout_write(const char *txt)
{
    unsigned t;
    sscanf(txt, "%u", &t);
    wifiScan_buf[7] = t;
    t >>= 8;
    wifiScan_buf[6] = t;
    return false;
}

const value_item_t wifiTimeout_item = { _ITEM_VALUE, 6, wifiTimeout_print, wifiTimeout_write};

bool AbortOnTimeout_read()
{
    return wifiScan_buf[8] == 1;
}

bool AbortOnTimeout_push()
{
    wifiScan_buf[8] ^= 1;
    return wifiScan_buf[8] == 1;
}

const toggle_item_t wifiAbort_item = { _ITEM_TOGGLE, "AbortOnTimeout", NULL, AbortOnTimeout_read, AbortOnTimeout_push};

bool wifiResultFormat_read()
{
    // false=full, true=basic
    return wifiResultFormatBasic;
}

bool wifiResultFormat_push()
{
    // false=full, true=basic
    wifiResultFormatBasic ^= true;
    return wifiResultFormatBasic;
}

const toggle_item_t wifiResultFormat_item = { _ITEM_TOGGLE,
    "full ",
    "basic",
    wifiResultFormat_read,
    wifiResultFormat_push
};

void Radio::gnssAutonomous_push()
{
    gnssAutonomous_buf[4] = 0;  // EffortMode
    radio.xfer(OPCODE_GNSS_AUTONOMOUS, 7, 0, gnssAutonomous_buf);
    log_printf("gnssAutonomous...\r\n");
}

const button_item_t Radio::gnssAutonomous_item = { _ITEM_BUTTON, "gnssAutonomous", gnssAutonomous_push };

void gnssAutonomous_time_print()
{
    unsigned t = Radio::radio.from_big_endian32(gnssAutonomous_buf);
    printf("%u", t);
}

bool gnssAutonomous_time_write(const char *txt)
{
    unsigned t;
    if (sscanf(txt, "%u", &t) == 1) {
        Radio::radio.to_big_endian32(t, gnssAutonomous_buf);
    }
    return false;
}


bool gnssAutoResultTimeStamp_read()
{
    return (gnssAutonomous_buf[5] & 0x01) == 0x01;
}
bool gnssAutoResultTimeStamp_push()
{
    gnssAutonomous_buf[5] ^= 0x01;
    return (gnssAutonomous_buf[5] & 0x01) == 0x01;
}
const toggle_item_t gnssAutoResultTimeStamp_item = { _ITEM_TOGGLE, "timestamp", NULL, gnssAutoResultTimeStamp_read, gnssAutoResultTimeStamp_push};

bool gnssAutoResultDoppler_read()
{
    return (gnssAutonomous_buf[5] & 0x02) == 0x02;
}
bool gnssAutoResultDoppler_push()
{
    gnssAutonomous_buf[5] ^= 0x02;
    return (gnssAutonomous_buf[5] & 0x02) == 0x02;
}
const toggle_item_t gnssAutoResultDoppler_item = { _ITEM_TOGGLE, "doppler", NULL, gnssAutoResultDoppler_read, gnssAutoResultDoppler_push};

bool gnssAutoResultBitChange_read()
{
    return (gnssAutonomous_buf[5] & 0x04) == 0x04;
}
bool gnssAutoResultBitChange_push()
{
    gnssAutonomous_buf[5] ^= 0x04;
    return (gnssAutonomous_buf[5] & 0x04) == 0x04;
}
const toggle_item_t gnssAutoResultBitChange_item = { _ITEM_TOGGLE, "bit change", NULL, gnssAutoResultBitChange_read, gnssAutoResultBitChange_push};

void gnssAutoNbSvMax_print()
{
    printf("%u", gnssAutonomous_buf[6]);
}

bool gnssAutoNbSvMax_write(const char *txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    gnssAutonomous_buf[6] = n;
    return false;
}

const value_item_t gnssAutoNbSvMax_item = { _ITEM_VALUE, 3, gnssAutoNbSvMax_print, gnssAutoNbSvMax_write};

const value_item_t gnssAutonomous_time_item = { _ITEM_VALUE, 9, gnssAutonomous_time_print, gnssAutonomous_time_write};

void Radio::gnssAssisted_push()
{
    gnssAssisted_buf[5] = 3; // ResultMask
    radio.xfer(OPCODE_GNSS_ASSISTED, 7, 0, gnssAssisted_buf);
    log_printf("gnssAssisted...\r\n");
}

const button_item_t Radio::gnssAssisted_item = { _ITEM_BUTTON, "gnssAssisted", gnssAssisted_push};

void gnssAssisted_time_print()
{
    unsigned t = Radio::radio.from_big_endian32(gnssAssisted_buf);
    printf("%u", t);
}

bool gnssAssisted_time_write(const char *txt)
{
    unsigned t;
    if (sscanf(txt, "%u", &t) == 1) {
        Radio::radio.to_big_endian32(t, gnssAssisted_buf);
    }
    return false;
}
const value_item_t gnssAssisted_time_item = { _ITEM_VALUE, 9, gnssAssisted_time_print, gnssAssisted_time_write};

bool gnssAssisted_effort_read()
{
    return gnssAssisted_buf[4] == 0x01;
}

bool gnssAssisted_effort_push()
{
    gnssAssisted_buf[4] ^= 0x01;
    return gnssAssisted_buf[4] == 0x01;
}

const toggle_item_t gnssAssisted_effort_item = { _ITEM_TOGGLE,
    "lowPower",
    "best    ",
    gnssAssisted_effort_read,
    gnssAssisted_effort_push
};

void gnssAssisted_nbsvmax_print()
{
    printf("%u", gnssAssisted_buf[6]);
}

bool gnssAssisted_nbsvmax_write(const char *txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    gnssAssisted_buf[6] = n;
    return false;
}

const value_item_t gnssAssisted_nbsvmax_item = { _ITEM_VALUE, 3, gnssAssisted_nbsvmax_print, gnssAssisted_nbsvmax_write};

bool gnss_const_gps_enable_read()
{
    gnssConstellation_t gc;
    Radio::radio.memRegRead(REG_ADDR_GNSS_CONST, 1, &gc.dword);
    return gc.bits.gps;
}

bool gnss_const_gps_enable_push()
{
    uint8_t ConstellationBitMask = 0;
    gnssConstellation_t gc;
    Radio::radio.memRegRead(REG_ADDR_GNSS_CONST, 1, &gc.dword);
    if (gc.bits.gps)
        ConstellationBitMask &= ~1;
    else
        ConstellationBitMask |= 1;
    if (gc.bits.beidou)
        ConstellationBitMask |= 2;
    Radio::radio.xfer(OPCODE_GNSS_SET_CONSTELLATION, 1, 0, &ConstellationBitMask);
    return ConstellationBitMask & 1;
}

const toggle_item_t gnss_const_gps_enable_item = { _ITEM_TOGGLE, "GPS", NULL, gnss_const_gps_enable_read, gnss_const_gps_enable_push};

bool gnss_const_beidou_enable_read()
{
    gnssConstellation_t gc;
    Radio::radio.memRegRead(REG_ADDR_GNSS_CONST, 1, &gc.dword);
    return gc.bits.beidou;
}

bool gnss_const_beidou_enable_push()
{
    uint8_t ConstellationBitMask = 0;
    gnssConstellation_t gc;
    Radio::radio.memRegRead(REG_ADDR_GNSS_CONST, 1, &gc.dword);
    if (gc.bits.beidou)
        ConstellationBitMask &= ~2;
    else
        ConstellationBitMask |= 2;
    if (gc.bits.gps)
        ConstellationBitMask |= 1;
    Radio::radio.xfer(OPCODE_GNSS_SET_CONSTELLATION, 1, 0, &ConstellationBitMask);
    return ConstellationBitMask & 1;
}

const toggle_item_t gnss_const_beidou_enable_item = { _ITEM_TOGGLE, "BEIDOU", NULL, gnss_const_beidou_enable_read, gnss_const_beidou_enable_push};

bool gnssmode_read()
{
    gnssMode_t gm;
    Radio::radio.memRegRead(REG_ADDR_GNSS_MODE, 1, &gm.dword);
    return gm.bits.gnss_scan_single;
}

bool gnssmode_push()
{
    uint8_t buf = gnssmode_read();  // GnssSetMode takes 1 for dual-scan
    Radio::radio.xfer(OPCODE_GNSS_SET_MODE, 1, 0, &buf);
    return buf == 0;
}

const toggle_item_t gnss_mode_item = { _ITEM_TOGGLE,
    "dual  ",
    "single",
    gnssmode_read,
    gnssmode_push
};

const menu_t Radio::common_menu[] = {
    { {FIRST_CHIP_MENU_ROW,  1},          NULL, &PaSel_item, FLAG_MSGTYPE_ALL, &tx_dbm_item},
    { {FIRST_CHIP_MENU_ROW, 10},          NULL, &RegPASupply_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW, 30}, "PaDutyCycle:", &PaDutyCycle_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW, 47},     "PaHPSel:", &PaHPSel_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+1,  1}, "regulator:", &regulator_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+1,  18}, "tcxoVolts:", &tcxo_volts_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+1,  32}, "tcxoDelay(ms):", &tcxo_delay_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+1,  52}, "re-cal(hex):", &recalibrate_item, FLAG_MSGTYPE_ALL},

   //012345678901234567890

    { {FIRST_CHIP_MENU_ROW+2,  1}, "txTimeout(sec):", &txTimeout_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+2, 24},              NULL, &rxBufferStatus_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+2, 35},              NULL, &getStats_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+10,  1},          NULL, &wifiScan_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+10, 10},          NULL, &wifiType_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+10, 19},          NULL, &wifiAcqMode_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+10, 35},   "NbMaxRes:", &wifiNbMaxRes_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+10, 48}, "NbScanPerChan:", &wifiNbScanPerChan_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+11,  1}, "timeout(ms):", &wifiTimeout_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+11, 20},           NULL, &wifiAbort_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+11, 45}, "resultFormat:", &wifiResultFormat_item, FLAG_MSGTYPE_ALL},


    { {FIRST_CHIP_MENU_ROW+12,  1},        NULL, &wifi_ch14_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12,  6},        NULL, &wifi_ch13_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 11},        NULL, &wifi_ch12_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 16},        NULL, &wifi_ch11_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 21},        NULL, &wifi_ch10_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 26},        NULL, &wifi_ch9_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 31},        NULL, &wifi_ch8_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 36},        NULL, &wifi_ch7_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 41},        NULL, &wifi_ch6_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 46},        NULL, &wifi_ch5_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 51},        NULL, &wifi_ch4_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 56},        NULL, &wifi_ch3_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 61},        NULL, &wifi_ch2_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+12, 66},        NULL, &wifi_ch1_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+13,  1},        NULL, &gnss_const_gps_enable_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+13,  5},        NULL, &gnss_const_beidou_enable_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+13, 12},     "scanning:", &gnss_mode_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+14,  1},          NULL, &gnssAutonomous_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+14, 16},       "time:", &gnssAutonomous_time_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+14, 31},          NULL, &gnssAutoResultTimeStamp_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+14, 41},          NULL, &gnssAutoResultDoppler_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+14, 51},          NULL, &gnssAutoResultBitChange_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+14, 63},    "NbSvMax:", &gnssAutoNbSvMax_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+15,  1},          NULL, &gnssAssisted_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+15, 16},       "time:", &gnssAssisted_time_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+15, 30},     "effort:", &gnssAssisted_effort_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+15, 50},   "NbSvMax::", &gnssAssisted_nbsvmax_item, FLAG_MSGTYPE_ALL},

   //0123456789012345
    { {0, 0}, NULL, NULL }
};

void Radio::gfsk_bitrate_print()
{
    uint32_t u32;
    unsigned hz;
    radio.memRegRead(REG_ADDR_GFSK_BITRATE, 1, &u32);
    hz = GFSK_BITRATE_NUMERATOR / u32;
    printf("%u", hz);
    radio.to_big_endian32(hz, gfsk_mp_buf);
}

bool Radio::gfsk_bitrate_write(const char* txt)
{
    unsigned bps;

    if (sscanf(txt, "%u", &bps) == 1) {
        radio.to_big_endian32(bps, gfsk_mp_buf);
        radio.xfer(OPCODE_SET_MODULATION, 10, 0, gfsk_mp_buf);
    }
    return false;
}

const value_item_t Radio::gfsk_bitrate_item = { _ITEM_VALUE, 8, gfsk_bitrate_print, gfsk_bitrate_write};

static const char* const gfsk_bts[] = {
    "off", // 0
    "0.3", // 1
    "0.5", // 2
    "0.7", // 3
    "1.0", // 4
    NULL
};

unsigned Radio::gfsk_bt_read(bool forWriting)
{
    gfskConfig0_t cfg0;
    radio.memRegRead(REG_ADDR_GFSK_CFG0, 1, &cfg0.dword);
    if (cfg0.bits.shaping_en) {
        switch (cfg0.bits.bt) {
            case 0: /* off */ gfsk_mp_buf[4] = GFSK_BT_OFF; break;
            case 1: /* 0.3 */ gfsk_mp_buf[4] = GFSK_BT_0_3; break;
            case 2: /* 0.5 */ gfsk_mp_buf[4] = GFSK_BT_0_5; break;
            case 3: /* 0.7 */ gfsk_mp_buf[4] = GFSK_BT_0_7; break;
            case 4: /* 1.0 */ gfsk_mp_buf[4] = GFSK_BT_1_0; break;
        }
        return cfg0.bits.bt + 1;
    } else
        return 0;
}

menuMode_e Radio::gfsk_bt_write(unsigned sidx)
{
    switch (sidx) {
        case 0: gfsk_mp_buf[4] = GFSK_BT_OFF; break;
        case 1: gfsk_mp_buf[4] = GFSK_BT_0_3; break;
        case 2: gfsk_mp_buf[4] = GFSK_BT_0_5; break;
        case 3: gfsk_mp_buf[4] = GFSK_BT_0_7; break;
        case 4: gfsk_mp_buf[4] = GFSK_BT_1_0; break;
    }
    radio.xfer(OPCODE_SET_MODULATION, 10, 0, gfsk_mp_buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_bt_item = { _ITEM_DROPDOWN, gfsk_bts, gfsk_bts, gfsk_bt_read, gfsk_bt_write};

void Radio::gfsk_rxbw_print()
{
    unsigned n;
    uint8_t bwf;
    gfskBW_t bw_reg;
    radio.memRegRead(REG_ADDR_GFSK_BWF, 1, &bw_reg.dword);
    bwf = bw_reg.bits.bwf_hi;
    bwf <<= 3;
    bwf |= bw_reg.bits.bwf_lo;
    gfsk_mp_buf[5] = bwf;

    for (n = 0; n < NB_RXBW; n++) {
        if (rxbws[n].bwf == bwf) {
            printf("%u", rxbws[n].hz);
            break;
        }
    }
}

rxbw_t Radio::rxbws[] = {
    /*  0 */ {   4800, GFSK_RX_BW_4800 },
    /*  1 */ {   5800, GFSK_RX_BW_5800 },
    /*  2 */ {   7300, GFSK_RX_BW_7300 },
    /*  3 */ {   9700, GFSK_RX_BW_9700 },
    /*  4 */ {  11700, GFSK_RX_BW_11700 },
    /*  5 */ {  14600, GFSK_RX_BW_14600 },
    /*  6 */ {  19500, GFSK_RX_BW_19500 },
    /*  7 */ {  23400, GFSK_RX_BW_23400 },
    /*  8 */ {  29300, GFSK_RX_BW_29300 },
    /*  9 */ {  39000, GFSK_RX_BW_39000 },
    /* 10 */ {  46900, GFSK_RX_BW_46900 },
    /* 11 */ {  58600, GFSK_RX_BW_58600 },
    /* 12 */ {  78200, GFSK_RX_BW_78200 },
    /* 13 */ {  93800, GFSK_RX_BW_93800 },
    /* 14 */ { 117300, GFSK_RX_BW_117300 },
    /* 15 */ { 156200, GFSK_RX_BW_156200 },
    /* 16 */ { 187200, GFSK_RX_BW_187200 },
    /* 17 */ { 234300, GFSK_RX_BW_234300 },
    /* 18 */ { 312000, GFSK_RX_BW_312000 },
    /* 19 */ { 373600, GFSK_RX_BW_373600 },
    /* 20 */ { 467000, GFSK_RX_BW_467000 }
};

bool Radio::gfsk_rxbw_write(const char* txt)
{
    unsigned n, rxbw;
    if (sscanf(txt, "%u", &rxbw) == 1) {
        for (n = 1; n < NB_RXBW; n++) {
            log_printf("%u) %u > %u\r\n", n, rxbw, rxbws[n].hz);
            if (rxbw >= rxbws[n-1].hz && rxbw < rxbws[n].hz) {
                gfsk_mp_buf[5] = rxbws[n-1].bwf;
                log_printf("RXBWset %u<-%u %02x\r\n", rxbws[n-1].hz, rxbw, gfsk_mp_buf[5]);
                break;
            }
        }
        radio.xfer(OPCODE_SET_MODULATION, 10, 0, gfsk_mp_buf);
    }
    return false;
}

const value_item_t Radio::gfsk_rxbw_item = { _ITEM_VALUE, 8, gfsk_rxbw_print, gfsk_rxbw_write};

void Radio::gfsk_fdev_print()
{
    unsigned hz;
    uint32_t u32;
    radio.memRegRead(REG_ADDR_GFSK_FDEV, 1, &u32);
    hz = my_round(u32 * FREQ_STEP);
    printf("%u", hz);
    radio.to_big_endian32(hz, gfsk_mp_buf+6);
}

bool Radio::gfsk_fdev_write(const char* txt)
{
    unsigned hz;
    if (sscanf(txt, "%u", &hz) == 1) {
        radio.to_big_endian32(hz, gfsk_mp_buf+6);
        radio.xfer(OPCODE_SET_MODULATION, 10, 0, gfsk_mp_buf);
    }
    return false;
}

const value_item_t Radio::gfsk_fdev_item = { _ITEM_VALUE, 8, gfsk_fdev_print, gfsk_fdev_write};

void Radio::gfsk_pblLen_print()
{
    unsigned n;
    gfskConfig1_t cfg1;
    radio.memRegRead(REG_ADDR_GFSK_CFG1, 1, &cfg1.dword);
    printf("%u", cfg1.bits.preamble_length);
    n = cfg1.bits.preamble_length;
    gfsk_pp_buf[1] = n;
    n >>= 8;
    gfsk_pp_buf[0] = n;
}

bool Radio::gfsk_pblLen_write(const char* txt)
{
    unsigned n;
    if (sscanf(txt, "%u", &n) == 1) {
        radio.to_big_endian16(n, gfsk_pp_buf);
        radio.xfer(OPCODE_SET_PACKET_PARAM, 9, 0, gfsk_pp_buf);
    }
    return false;
}

const value_item_t Radio::gfsk_pblLen_item = { _ITEM_VALUE, 5, gfsk_pblLen_print, gfsk_pblLen_write};

static const char* const fsk_detlens[] = {
    " off  ", // GFSK_PBLDET_LENGTH_OFF  0x00
    " 8bits", // GFSK_PBLDET_LENGTH_8    0x04
    "16bits", // GFSK_PBLDET_LENGTH_16   0x05
    "24bits", // GFSK_PBLDET_LENGTH_24   0x06
    "32bits", // GFSK_PBLDET_LENGTH_32   0x07
    NULL
};

unsigned Radio::gfsk_pblDetLen_read(bool forWriting)
{
    gfskConfig1_t cfg1;
    radio.memRegRead(REG_ADDR_GFSK_CFG1, 1, &cfg1.dword);
    if (cfg1.bits.preamble_det_enable) {
        gfsk_pp_buf[2] = cfg1.bits.preamble_det_len + 4;
        return cfg1.bits.preamble_det_len + 1;
    } else {
        gfsk_pp_buf[2] = 0;
        return 0;
    }
}

menuMode_e Radio::gfsk_pblDetLen_write(unsigned sidx)
{
    if (sidx == 0)
        gfsk_pp_buf[2] = 0;
    else
        gfsk_pp_buf[2] = sidx + 4;

    radio.xfer(OPCODE_SET_PACKET_PARAM, 9, 0, gfsk_pp_buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_pblDetLen_item = { _ITEM_DROPDOWN, fsk_detlens, fsk_detlens, gfsk_pblDetLen_read, gfsk_pblDetLen_write};

void Radio::gfsk_syncWord_print(void)
{
    uint32_t dword[2];
    radio.memRegRead(REG_ADDR_GFSK_SYNC_LO, 2, dword);
    printf("%08lx", dword[1]);
    printf("%08lx", dword[0]);
}

bool Radio::gfsk_syncWord_write(const char* txt)
{
    uint8_t buf[8];
    const char* ptr;
    const char* endPtr;
    unsigned o, n = 0;

    endPtr = txt + strlen(txt);
    for (ptr = txt; sscanf(ptr, "%02x", &o) == 1; ) {
        buf[n++] = o;
        ptr += 2;
        if (ptr >= endPtr)
            break;
    }

    radio.xfer(OPCODE_SET_GFSK_SYNC_WORD, 8, 0, buf);
    return false;
}

const value_item_t Radio::gfsk_syncWord_item = { _ITEM_VALUE, 17, gfsk_syncWord_print, gfsk_syncWord_write};

static const char* const addrcomps[] = {
    "          off        ",
    "NodeAddress          ",
    "NodeAddress+broadcast",
    NULL
};

unsigned Radio::gfsk_addrcomp_read(bool forWriting)
{
    gfskConfig4_t cfg4;
    radio.memRegRead(REG_ADDR_GFSK_PAYLOAD_LENGTH_B, 1, &cfg4.dword);
    gfsk_pp_buf[4] = cfg4.bits.addr_comp;
    return cfg4.bits.addr_comp;
}

menuMode_e Radio::gfsk_addrcomp_write(unsigned sidx)
{
    gfsk_pp_buf[4] = sidx;
    radio.xfer(OPCODE_SET_PACKET_PARAM, 9, 0, gfsk_pp_buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_addrcomp_item = { _ITEM_DROPDOWN, addrcomps, addrcomps, gfsk_addrcomp_read, gfsk_addrcomp_write};

bool Radio::gfsk_varlen_read()
{
    gfskConfig3_t cfg3;
    radio.memRegRead(REG_ADDR_GFSK_CFG3, 1, &cfg3.dword);
    gfsk_pp_buf[5] = cfg3.bits.variable_length;
    return cfg3.bits.variable_length;
}

bool Radio::gfsk_varlen_push()
{
    gfsk_pp_buf[5] ^= 1;
    radio.xfer(OPCODE_SET_PACKET_PARAM, 9, 0, gfsk_pp_buf);
    return gfsk_pp_buf[5];
}

const toggle_item_t Radio::gfsk_varlen_item = { _ITEM_TOGGLE, "fixLen", "varLen", gfsk_varlen_read, gfsk_varlen_push};

void Radio::gfsk_syncLen_print()
{
    gfskConfig2_t cfg2;
    radio.memRegRead(REG_ADDR_GFSK_CFG2, 1, &cfg2.dword);
    gfsk_pp_buf[3] = cfg2.bits.sync_word_length;
    printf("%u", cfg2.bits.sync_word_length);
}

bool Radio::gfsk_syncLen_write(const char *txt)
{
    unsigned n;
    if (sscanf(txt, "%u", &n) == 1) {
        gfsk_pp_buf[3] = n;
        radio.xfer(OPCODE_SET_PACKET_PARAM, 9, 0, gfsk_pp_buf);
    }
    return false;
}

const value_item_t Radio::gfsk_syncLen_item = { _ITEM_VALUE, 4, gfsk_syncLen_print, gfsk_syncLen_write}; 

const char* const Radio::gfsk_crcType_strs[] = 
{
    /* 0 */ "    OFF   ",
    /* 1 */ "1_BYTE    ",
    /* 2 */ "2_BYTE    ",
    /* 3 */ "1_BYTE_INV",
    /* 4 */ "2_BYTE_INV",
    /* 5 */ NULL
};

unsigned Radio::gfsk_crcType_read(bool for_writing)
{
    unsigned ret;
    gfskConfig5_t cfg5;
    radio.memRegRead(REG_ADDR_GFSK_CFG5, 1, &cfg5.dword);
    // gfsk_pp_buf[7]: bit2:inv  bit1:twobyte  bit0:disabled
    if (cfg5.bits.crc_off)
        gfsk_pp_buf[7] |= 1;
    else
        gfsk_pp_buf[7] &= ~1;

    if (cfg5.bits.crc_size)
        gfsk_pp_buf[7] |= 2;
    else
        gfsk_pp_buf[7] &= ~2;

    if (cfg5.bits.crc_invert)
        gfsk_pp_buf[7] |= 4;
    else
        gfsk_pp_buf[7] &= ~4;

    switch (gfsk_pp_buf[7]) {
        case GFSK_CRC_OFF: ret = 0; break;
        case GFSK_CRC_1_BYTE: ret = 1; break;
        case GFSK_CRC_2_BYTE: ret = 2; break;
        case GFSK_CRC_1_BYTE_INV: ret = 3; break;
        case GFSK_CRC_2_BYTE_INV: ret = 4; break;
        default: ret = 5; break;
    }
    return ret;
}

menuMode_e Radio::gfsk_crcType_write(unsigned sidx)
{
    switch (sidx) {
        case 0: gfsk_pp_buf[7] = GFSK_CRC_OFF; break;
        case 1: gfsk_pp_buf[7] = GFSK_CRC_1_BYTE; break;
        case 2: gfsk_pp_buf[7] = GFSK_CRC_2_BYTE; break;
        case 3: gfsk_pp_buf[7] = GFSK_CRC_1_BYTE_INV; break;
        case 4: gfsk_pp_buf[7] = GFSK_CRC_2_BYTE_INV; break;
    }
    radio.xfer(OPCODE_SET_PACKET_PARAM, 9, 0, gfsk_pp_buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_crcType_item = { _ITEM_DROPDOWN, gfsk_crcType_strs, gfsk_crcType_strs, gfsk_crcType_read, gfsk_crcType_write };

bool Radio::gfsk_dcfree_read(void)
{
    gfskConfig5_t cfg5;
    radio.memRegRead(REG_ADDR_GFSK_CFG5, 1, &cfg5.dword);
    gfsk_pp_buf[8] = cfg5.bits.whitening_enable;
    return cfg5.bits.whitening_enable;
}

bool Radio::gfsk_dcfree_push(void)
{
    gfsk_pp_buf[8] ^= 1;
    radio.xfer(OPCODE_SET_PACKET_PARAM, 9, 0, gfsk_pp_buf);
    return gfsk_pp_buf[8] == 1;
}

const toggle_item_t Radio::gfsk_dcfree_item = { _ITEM_TOGGLE, "off", "on ", gfsk_dcfree_read, gfsk_dcfree_push};

void Radio::gfsk_crcinit_print()
{
    radio.memRegRead(REG_ADDR_GFSK_CRC_INIT, 1, &gfsk_crc_Poly);
    printf("%04x", (unsigned)gfsk_crc_Poly);
}

bool Radio::gfsk_crcinit_write(const char *txt)
{
    uint8_t buf[8];
    unsigned crcInit;
    sscanf(txt, "%x", &crcInit);
    gfsk_crc_initValue = crcInit;
    radio.to_big_endian32(gfsk_crc_initValue, buf);
    radio.to_big_endian32(gfsk_crc_Poly, buf+4);
    radio.xfer(OPCODE_SET_GFSK_CRC_PARAMS, 8, 0, buf);
    return false;
}

const value_item_t Radio::gfsk_crcinit_item = { _ITEM_VALUE, 4, gfsk_crcinit_print, gfsk_crcinit_write}; 

void Radio::gfsk_crcpoly_print()
{
    radio.memRegRead(REG_ADDR_GFSK_CRC_POLY, 1, &gfsk_crc_Poly);
    printf("%04x", (unsigned)gfsk_crc_Poly);
}

bool Radio::gfsk_crcpoly_write(const char *txt)
{
    uint8_t buf[8];
    unsigned crcPoly;
    sscanf(txt, "%x", &crcPoly);
    gfsk_crc_Poly = crcPoly;
    radio.to_big_endian32(gfsk_crc_initValue, buf);
    radio.to_big_endian32(gfsk_crc_Poly, buf+4);
    radio.xfer(OPCODE_SET_GFSK_CRC_PARAMS, 8, 0, buf);
    return false;
}

const value_item_t Radio::gfsk_crcpoly_item = { _ITEM_VALUE, 4, gfsk_crcpoly_print, gfsk_crcpoly_write}; 

const menu_t Radio::gfsk_menu[] = {
    { {FIRST_CHIP_MENU_ROW+2,  1},    "bps:", &gfsk_bitrate_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 15},    "bt:",       &gfsk_bt_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 23},   "rxbw:",    &gfsk_rxbw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 39},   "fdev:",    &gfsk_fdev_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+3,  1}, "PreambleLength:",  &gfsk_pblLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 21}, "PreambleDetectorLength:",  &gfsk_pblDetLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4,  1}, "syncWord:",  &gfsk_syncWord_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 29},     "bits:",  &gfsk_syncLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 38},      "CRC:",  &gfsk_crcType_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 55}, "AddrComp:",   &gfsk_addrcomp_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+5,  1},        NULL,   &gfsk_varlen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5, 10},   "whiten:",   &gfsk_dcfree_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5, 23},  "crcInit:",   &gfsk_crcinit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5, 36},  "crcPoly:",   &gfsk_crcpoly_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+6,  1},          NULL, &dbg_item, FLAG_MSGTYPE_ALL},
    { {0, 0}, NULL, NULL }
};

static const char* const lora_bwstrs[] = {
   /* 0 */ "  7.8KHz",
   /* 1 */ " 15.6KHz",
   /* 2 */ "31,25KHz",
   /* 3 */ " 62.5KHz",
   /* 4 */ "  125KHz",
   /* 5 */ "  250KHz",
   /* 6 */ "  500KHz",
   /* 7 */ " 1000KHz",
   /* 0 */ NULL
};

unsigned Radio::lora_bw_read(bool forWriting)
{
    loraConfig0_t cfg0;
    radio.memRegRead(REG_ADDR_LORA_CONFIG0, 1, &cfg0.dword);
    return cfg0.bits.modem_bw;
}

menuMode_e Radio::lora_bw_write(unsigned sidx)
{
    lora_mp_buf[1] = sidx;
    radio.xfer(OPCODE_SET_MODULATION, 4, 0, lora_mp_buf);
    return MENUMODE_REDRAW;
}

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

void Radio::lora_sf_print()
{
    loraConfig0_t cfg0;
    radio.memRegRead(REG_ADDR_LORA_CONFIG0, 1, &cfg0.dword);
    lora_mp_buf[0] = cfg0.bits.modem_sf;
    printf("%u", cfg0.bits.modem_sf);
}

bool Radio::lora_sf_write(const char* str)
{
    unsigned sf;
    if (sscanf(str, "%u", &sf) == 1) {
        lora_mp_buf[0] = sf;
        radio.xfer(OPCODE_SET_MODULATION, 4, 0, lora_mp_buf);
    }
    return false;
}

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

void Radio::lora_pblLen_print()
{
    unsigned pl;
    loraConfigC_t cfgc;
    radio.memRegRead(REG_ADDR_LORA_CONFIGC, 1, &cfgc.dword);
    printf("%u", cfgc.bits.preamble_length);
    pl = cfgc.bits.preamble_length;
    lora_pp_buf[1] = pl & 0xff;
    pl >>= 8;
    lora_pp_buf[0] = pl;
}

bool Radio::lora_pblLen_write(const char* txt)
{
    unsigned n;
    if (sscanf(txt, "%u", &n) == 1) {
        radio.to_big_endian16(n, lora_pp_buf); 
        radio.xfer(OPCODE_SET_PACKET_PARAM, 6, 0, lora_pp_buf);
    }
    return false;
}

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

bool Radio::lora_headerType_read()
{
    loraConfig0_t cfg0;
    radio.memRegRead(REG_ADDR_LORA_CONFIG0, 1, &cfg0.dword);
    lora_pp_buf[2] = cfg0.bits.implicit_header;
    return cfg0.bits.implicit_header;
}

bool Radio::lora_headerType_push()
{
    bool ret;
    if (lora_pp_buf[2]) {
        lora_pp_buf[2] = 0;
        ret = false;
    } else {
        lora_pp_buf[2] = 1;
        ret = true;
    }
    radio.xfer(OPCODE_SET_PACKET_PARAM, 6, 0, lora_pp_buf);
    return ret;
}

const toggle_item_t Radio::lora_headerType_item = { _ITEM_TOGGLE, "EXPLICIT", "IMPLICIT", lora_headerType_read, lora_headerType_push};

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

unsigned Radio::lora_cr_read(bool forWriting)
{
    loraConfig0_t cfg0;
    radio.memRegRead(REG_ADDR_LORA_CONFIG0, 1, &cfg0.dword);
    lora_mp_buf[2] = cfg0.bits.coding_rate;
    if (lora_mp_buf[2] > 7)
        return 7;
    else
        return lora_mp_buf[2];
}

menuMode_e Radio::lora_cr_write(unsigned sidx)
{
    lora_mp_buf[2] = sidx + 1;
    radio.xfer(OPCODE_SET_MODULATION, 4, 0, lora_mp_buf);
    return MENUMODE_REDRAW;
}

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

bool Radio::ppmOffset_read()
{
    loraConfig0_t cfg0;
    radio.memRegRead(REG_ADDR_LORA_CONFIG0, 1, &cfg0.dword);
    lora_mp_buf[3] = cfg0.bits.ppm_offset;
    return lora_mp_buf[3];
}

bool Radio::ppmOffset_push()
{
    bool ret;
    if (lora_mp_buf[3]) {
        lora_mp_buf[3] = 0;
        ret = false;
    } else {
        lora_mp_buf[3] = 1;
        ret = true;
    }
    radio.xfer(OPCODE_SET_MODULATION, 4, 0, lora_mp_buf);
    return ret;
}

const toggle_item_t Radio::lora_ppmOffset_item = { _ITEM_TOGGLE, "LowDatarateOptimize", NULL, ppmOffset_read, ppmOffset_push};

bool Radio::lora_crcon_read()
{
    loraConfig0_t cfg0;
    radio.memRegRead(REG_ADDR_LORA_CONFIG0, 1, &cfg0.dword);
    lora_pp_buf[4] = cfg0.bits.crc_on;
    return cfg0.bits.crc_on;
}

bool Radio::lora_crcon_push()
{
    bool ret;
    if (lora_pp_buf[4]) {
        lora_pp_buf[4] = 0;
        ret = false;
    } else {
        lora_pp_buf[4] = 1;
        ret = true;
    }
    radio.xfer(OPCODE_SET_PACKET_PARAM, 6, 0, lora_pp_buf);
    return ret;
}

const toggle_item_t Radio::lora_crcon_item = { _ITEM_TOGGLE,
    "crc-off",
    " CrcOn ",
    lora_crcon_read,
    lora_crcon_push
};

bool Radio::lora_inviq_read()
{
    loraConfigA_t cfgA;
    //loraConfigB_t cfgB;
    radio.memRegRead(REG_ADDR_LORA_CONFIGA, 1, &cfgA.dword);
    /*radio.memRegRead(REG_ADDR_LORA_CONFIGB, 1, buf);
    cfgB.dword = radio.from_big_endian32(buf);*/
    //TODO which one is RX vs TX: cfgA.bits.invertIQ, cfgB.bits.invertIQ
    lora_pp_buf[5] = cfgA.bits.invertIQ;
    /*if (cfgA.bits.invertIQ != cfgB.bits.invertIQ)
        log_printf("invIQ %u %u\r\n", cfgA.bits.invertIQ, cfgB.bits.invertIQ);*/
    return lora_pp_buf[5];
}

bool Radio::lora_inviq_push()
{
    lora_pp_buf[5] ^= 1;
    radio.xfer(OPCODE_SET_PACKET_PARAM, 6, 0, lora_pp_buf);
    return lora_pp_buf[5] == 1;
}

const toggle_item_t Radio::lora_inviq_item = { _ITEM_TOGGLE,
    "    IQ    ",
    "invertedIQ",
    lora_inviq_read, lora_inviq_push
};

bool Radio::lora_sync_read()
{
    loraSync_t sync;
    radio.memRegRead(REG_ADDR_LORA_SYNC, 1, &sync.dword);
    if (sync.bits.ppg_a == 2 && sync.bits.ppg_b == 4)
        return false;   // private
    else if (sync.bits.ppg_a == 6 && sync.bits.ppg_b == 8)
        return true;   // public
    else
        return false;   // unknown
    
}

bool Radio::lora_sync_push()
{
    uint8_t buf;
    if (lora_sync_read())
        buf = 0;
    else
        buf = 1;

    radio.xfer(OPCODE_SET_LORA_PUBLIC_NETWORK, 1, 0, &buf);
    return buf;
}

const toggle_item_t Radio::lora_sync_item = { _ITEM_TOGGLE,
    "private",
    "public ",
    lora_sync_read,
    lora_sync_push
};

const menu_t Radio::lora_menu[] = {
    { {FIRST_CHIP_MENU_ROW+2,  1},   NULL,      &lora_bw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 12},  "sf:",      &lora_sf_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 19},  "cr:",      &lora_cr_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 37},   NULL, &lora_ppmOffset_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+3,  1}, "PreambleLength:", &lora_pblLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 23},              NULL, &lora_headerType_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 33},              NULL, &lora_crcon_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 43},              NULL, &lora_inviq_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 55},           "sync:", &lora_sync_item, FLAG_MSGTYPE_ALL },

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

void Radio::dbg_push()
{
    log_printf("dbg\r\n");
}

const button_item_t Radio::dbg_item  = { _ITEM_BUTTON, "DBG", dbg_push };

const button_item_t      rfswEn_item = { _ITEM_BUTTON, " RfSwEnable:", NULL};
const button_item_t rfswStbyCfg_item = { _ITEM_BUTTON, "rfswStbyCfg:", NULL};
const button_item_t   rfswRxCfg_item = { _ITEM_BUTTON, "  rfswRxCfg:", NULL};
const button_item_t   rfswTxCfg_item = { _ITEM_BUTTON, "  rfswTxCfg:", NULL};
const button_item_t rfswTxHPCfg_item = { _ITEM_BUTTON, "rfswTxHPCfg:", NULL};
const button_item_t rfswGnssCfg_item = { _ITEM_BUTTON, "rfswGnssCfg:", NULL};
const button_item_t rfswWifiCfg_item = { _ITEM_BUTTON, "rfswWifiCfg:", NULL};


bool Radio::DIO10en_read()
{
    dioEnable_t dio;
    radio.memRegRead(REG_ADDR_DIO10, 1, &dio.dword);
    if (dio.bits.enable)
        dioBuf[DIO_en_IDX] |= DIO10_BIT;
    else
        dioBuf[DIO_en_IDX] &= ~DIO10_BIT;
    return dio.bits.enable;
}

bool Radio::DIO8en_read()
{
    dioEnable_t dio;
    radio.memRegRead(REG_ADDR_DIO8, 1, &dio.dword);
    if (dio.bits.enable)
        dioBuf[DIO_en_IDX] |= DIO8_BIT;
    else
        dioBuf[DIO_en_IDX] &= ~DIO8_BIT;
    return dio.bits.enable;
}

bool Radio::DIO7en_read()
{
    dioEnable_t dio;
    radio.memRegRead(REG_ADDR_DIO7, 1, &dio.dword);
    if (dio.bits.enable)
        dioBuf[DIO_en_IDX] |= DIO7_BIT;
    else
        dioBuf[DIO_en_IDX] &= ~DIO7_BIT;
    return dio.bits.enable;
}

bool Radio::DIO6en_read()
{
    dioEnable_t dio;
    radio.memRegRead(REG_ADDR_DIO6, 1, &dio.dword);
    if (dio.bits.enable)
        dioBuf[DIO_en_IDX] |= DIO6_BIT;
    else
        dioBuf[DIO_en_IDX] &= ~DIO6_BIT;
    return dio.bits.enable;
}

bool Radio::DIO5en_read()
{
    dioEnable_t dio;
    radio.memRegRead(REG_ADDR_DIO5, 1, &dio.dword);
    if (dio.bits.enable)
        dioBuf[DIO_en_IDX] |= DIO5_BIT;
    else
        dioBuf[DIO_en_IDX] &= ~DIO5_BIT;
    return dio.bits.enable;
}

bool Radio::DIO10en_push()
{
    dioBuf[DIO_en_IDX] ^= DIO10_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return dioBuf[DIO_en_IDX] == DIO10_BIT;
}

bool Radio::DIO8en_push()
{
    dioBuf[DIO_en_IDX] ^= DIO8_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return dioBuf[DIO_en_IDX] == DIO8_BIT;
}

bool Radio::DIO7en_push()
{
    dioBuf[DIO_en_IDX] ^= DIO7_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return dioBuf[DIO_en_IDX] == DIO7_BIT;
}

bool Radio::DIO6en_push()
{
    dioBuf[DIO_en_IDX] ^= DIO6_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return dioBuf[DIO_en_IDX] == DIO6_BIT;
}

bool Radio::DIO5en_push()
{
    dioBuf[DIO_en_IDX] ^= DIO5_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return dioBuf[DIO_en_IDX] == DIO5_BIT;
}

const toggle_item_t Radio::DIO5en_item = { _ITEM_TOGGLE, "DIO5", NULL, DIO5en_read, DIO5en_push};
const toggle_item_t Radio::DIO6en_item = { _ITEM_TOGGLE, "DIO6", NULL, DIO6en_read, DIO6en_push};
const toggle_item_t Radio::DIO7en_item = { _ITEM_TOGGLE, "DIO7", NULL, DIO7en_read, DIO7en_push};
const toggle_item_t Radio::DIO8en_item = { _ITEM_TOGGLE, "DIO8", NULL, DIO8en_read, DIO8en_push};
const toggle_item_t Radio::DIO10en_item = { _ITEM_TOGGLE, "DIO10", NULL, DIO10en_read, DIO10en_push};

bool Radio::DIO10stby_read()
{
    return dioBuf[DIO_stby_IDX] == DIO10_BIT;
}

bool Radio::DIO10stby_push()
{
    dioBuf[DIO_stby_IDX] ^= DIO10_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO10stby_read();
}

bool Radio::DIO8stby_read()
{
    return dioBuf[DIO_stby_IDX] == DIO8_BIT;
}

bool Radio::DIO8stby_push()
{
    dioBuf[DIO_stby_IDX] ^= DIO8_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO8stby_read();
}

bool Radio::DIO7stby_read()
{
    return dioBuf[DIO_stby_IDX] == DIO7_BIT;
}

bool Radio::DIO7stby_push()
{
    dioBuf[DIO_stby_IDX] ^= DIO7_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO7stby_read();
}

bool Radio::DIO6stby_read()
{
    return dioBuf[DIO_stby_IDX] == DIO6_BIT;
}

bool Radio::DIO6stby_push()
{
    dioBuf[DIO_stby_IDX] ^= DIO6_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO6stby_read();
}

bool Radio::DIO5stby_read()
{
    return dioBuf[DIO_stby_IDX] == DIO5_BIT;
}

bool Radio::DIO5stby_push()
{
    dioBuf[DIO_stby_IDX] ^= DIO5_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO5stby_read();
}

const toggle_item_t Radio::DIO5stby_item = { _ITEM_TOGGLE, "DIO5", NULL, DIO5stby_read, DIO5stby_push};
const toggle_item_t Radio::DIO6stby_item = { _ITEM_TOGGLE, "DIO6", NULL, DIO6stby_read, DIO6stby_push};
const toggle_item_t Radio::DIO7stby_item = { _ITEM_TOGGLE, "DIO7", NULL, DIO7stby_read, DIO7stby_push};
const toggle_item_t Radio::DIO8stby_item = { _ITEM_TOGGLE, "DIO8", NULL, DIO8stby_read, DIO8stby_push};
const toggle_item_t Radio::DIO10stby_item = { _ITEM_TOGGLE, "DIO10", NULL, DIO10stby_read, DIO10stby_push};

bool Radio::DIO10rx_read()
{
    return dioBuf[DIO_rx_IDX] == DIO10_BIT;
}

bool Radio::DIO10rx_push()
{
    dioBuf[DIO_rx_IDX] ^= DIO10_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO10rx_read();
}

bool Radio::DIO8rx_read()
{
    return dioBuf[DIO_rx_IDX] == DIO8_BIT;
}

bool Radio::DIO8rx_push()
{
    dioBuf[DIO_rx_IDX] ^= DIO8_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO8rx_read();
}

bool Radio::DIO7rx_read()
{
    return dioBuf[DIO_rx_IDX] == DIO7_BIT;
}

bool Radio::DIO7rx_push()
{
    dioBuf[DIO_rx_IDX] ^= DIO7_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO7rx_read();
}

bool Radio::DIO6rx_read()
{
    return dioBuf[DIO_rx_IDX] == DIO6_BIT;
}

bool Radio::DIO6rx_push()
{
    dioBuf[DIO_rx_IDX] ^= DIO6_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO6rx_read();
}

bool Radio::DIO5rx_read()
{
    return dioBuf[DIO_rx_IDX] == DIO5_BIT;
}

bool Radio::DIO5rx_push()
{
    dioBuf[DIO_rx_IDX] ^= DIO5_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO5rx_read();
}

const toggle_item_t Radio::DIO5rx_item = { _ITEM_TOGGLE, "DIO5", NULL, DIO5rx_read, DIO5rx_push};
const toggle_item_t Radio::DIO6rx_item = { _ITEM_TOGGLE, "DIO6", NULL, DIO6rx_read, DIO6rx_push};
const toggle_item_t Radio::DIO7rx_item = { _ITEM_TOGGLE, "DIO7", NULL, DIO7rx_read, DIO7rx_push};
const toggle_item_t Radio::DIO8rx_item = { _ITEM_TOGGLE, "DIO8", NULL, DIO8rx_read, DIO8rx_push};
const toggle_item_t Radio::DIO10rx_item = { _ITEM_TOGGLE, "DIO10", NULL, DIO10rx_read, DIO10rx_push};

bool Radio::DIO10tx_read()
{
    return dioBuf[DIO_tx_IDX] == DIO10_BIT;
}

bool Radio::DIO10tx_push()
{
    dioBuf[DIO_tx_IDX] ^= DIO10_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO10tx_read();
}

bool Radio::DIO8tx_read()
{
    return dioBuf[DIO_tx_IDX] == DIO8_BIT;
}

bool Radio::DIO8tx_push()
{
    dioBuf[DIO_tx_IDX] ^= DIO8_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO8tx_read();
}

bool Radio::DIO7tx_read()
{
    return dioBuf[DIO_tx_IDX] == DIO7_BIT;
}

bool Radio::DIO7tx_push()
{
    dioBuf[DIO_tx_IDX] ^= DIO7_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO7tx_read();
}

bool Radio::DIO6tx_read()
{
    return dioBuf[DIO_tx_IDX] == DIO6_BIT;
}

bool Radio::DIO6tx_push()
{
    dioBuf[DIO_tx_IDX] ^= DIO6_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO6tx_read();
}

bool Radio::DIO5tx_read()
{
    return dioBuf[DIO_tx_IDX] == DIO5_BIT;
}

bool Radio::DIO5tx_push()
{
    dioBuf[DIO_tx_IDX] ^= DIO5_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO5tx_read();
}

const toggle_item_t Radio::DIO5tx_item = { _ITEM_TOGGLE, "DIO5", NULL, DIO5tx_read, DIO5tx_push};
const toggle_item_t Radio::DIO6tx_item = { _ITEM_TOGGLE, "DIO6", NULL, DIO6tx_read, DIO6tx_push};
const toggle_item_t Radio::DIO7tx_item = { _ITEM_TOGGLE, "DIO7", NULL, DIO7tx_read, DIO7tx_push};
const toggle_item_t Radio::DIO8tx_item = { _ITEM_TOGGLE, "DIO8", NULL, DIO8tx_read, DIO8tx_push};
const toggle_item_t Radio::DIO10tx_item = { _ITEM_TOGGLE, "DIO10", NULL, DIO10tx_read, DIO10tx_push};

bool Radio::DIO10txhp_read()
{
    return dioBuf[DIO_txhp_IDX] == DIO10_BIT;
}

bool Radio::DIO10txhp_push()
{
    dioBuf[DIO_txhp_IDX] ^= DIO10_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO10txhp_read();
}

bool Radio::DIO8txhp_read()
{
    return dioBuf[DIO_txhp_IDX] == DIO8_BIT;
}

bool Radio::DIO8txhp_push()
{
    dioBuf[DIO_txhp_IDX] ^= DIO8_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO8txhp_read();
}

bool Radio::DIO7txhp_read()
{
    return dioBuf[DIO_txhp_IDX] == DIO7_BIT;
}

bool Radio::DIO7txhp_push()
{
    dioBuf[DIO_txhp_IDX] ^= DIO7_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO7txhp_read();
}

bool Radio::DIO6txhp_read()
{
    return dioBuf[DIO_txhp_IDX] == DIO6_BIT;
}

bool Radio::DIO6txhp_push()
{
    dioBuf[DIO_txhp_IDX] ^= DIO6_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO6txhp_read();
}

bool Radio::DIO5txhp_read()
{
    return dioBuf[DIO_txhp_IDX] == DIO5_BIT;
}

bool Radio::DIO5txhp_push()
{
    dioBuf[DIO_txhp_IDX] ^= DIO5_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO5txhp_read();
}

const toggle_item_t Radio::DIO5txhp_item = { _ITEM_TOGGLE, "DIO5", NULL, DIO5txhp_read, DIO5txhp_push};
const toggle_item_t Radio::DIO6txhp_item = { _ITEM_TOGGLE, "DIO6", NULL, DIO6txhp_read, DIO6txhp_push};
const toggle_item_t Radio::DIO7txhp_item = { _ITEM_TOGGLE, "DIO7", NULL, DIO7txhp_read, DIO7txhp_push};
const toggle_item_t Radio::DIO8txhp_item = { _ITEM_TOGGLE, "DIO8", NULL, DIO8txhp_read, DIO8txhp_push};
const toggle_item_t Radio::DIO10txhp_item = { _ITEM_TOGGLE, "DIO10", NULL, DIO10txhp_read, DIO10txhp_push};

bool Radio::DIO10gnss_read()
{
    return dioBuf[DIO_gnss_IDX] == DIO10_BIT;
}

bool Radio::DIO10gnss_push()
{
    dioBuf[DIO_gnss_IDX] ^= DIO10_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO10gnss_read();
}

bool Radio::DIO8gnss_read()
{
    return dioBuf[DIO_gnss_IDX] == DIO8_BIT;
}

bool Radio::DIO8gnss_push()
{
    dioBuf[DIO_gnss_IDX] ^= DIO8_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO8gnss_read();
}

bool Radio::DIO7gnss_read()
{
    return dioBuf[DIO_gnss_IDX] == DIO7_BIT;
}

bool Radio::DIO7gnss_push()
{
    dioBuf[DIO_gnss_IDX] ^= DIO7_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO7gnss_read();
}

bool Radio::DIO6gnss_read()
{
    return dioBuf[DIO_gnss_IDX] == DIO6_BIT;
}

bool Radio::DIO6gnss_push()
{
    dioBuf[DIO_gnss_IDX] ^= DIO6_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO6gnss_read();
}

bool Radio::DIO5gnss_read()
{
    return dioBuf[DIO_gnss_IDX] == DIO5_BIT;
}

bool Radio::DIO5gnss_push()
{
    dioBuf[DIO_gnss_IDX] ^= DIO5_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO5gnss_read();
}

const toggle_item_t Radio::DIO5gnss_item = { _ITEM_TOGGLE, "DIO5", NULL, DIO5gnss_read, DIO5gnss_push};
const toggle_item_t Radio::DIO6gnss_item = { _ITEM_TOGGLE, "DIO6", NULL, DIO6gnss_read, DIO6gnss_push};
const toggle_item_t Radio::DIO7gnss_item = { _ITEM_TOGGLE, "DIO7", NULL, DIO7gnss_read, DIO7gnss_push};
const toggle_item_t Radio::DIO8gnss_item = { _ITEM_TOGGLE, "DIO8", NULL, DIO8gnss_read, DIO8gnss_push};
const toggle_item_t Radio::DIO10gnss_item = { _ITEM_TOGGLE, "DIO10", NULL, DIO10gnss_read, DIO10gnss_push};

bool Radio::DIO10wifi_read()
{
    return dioBuf[DIO_wifi_IDX] == DIO10_BIT;
}

bool Radio::DIO10wifi_push()
{
    dioBuf[DIO_wifi_IDX] ^= DIO10_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO10wifi_read();
}

bool Radio::DIO8wifi_read()
{
    return dioBuf[DIO_wifi_IDX] == DIO8_BIT;
}

bool Radio::DIO8wifi_push()
{
    dioBuf[DIO_wifi_IDX] ^= DIO8_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO8wifi_read();
}

bool Radio::DIO7wifi_read()
{
    return dioBuf[DIO_wifi_IDX] == DIO7_BIT;
}

bool Radio::DIO7wifi_push()
{
    dioBuf[DIO_wifi_IDX] ^= DIO7_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO7wifi_read();
}

bool Radio::DIO6wifi_read()
{
    return dioBuf[DIO_wifi_IDX] == DIO6_BIT;
}

bool Radio::DIO6wifi_push()
{
    dioBuf[DIO_wifi_IDX] ^= DIO6_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO6wifi_read();
}

bool Radio::DIO5wifi_read()
{
    return dioBuf[DIO_wifi_IDX] == DIO5_BIT;
}

bool Radio::DIO5wifi_push()
{
    dioBuf[DIO_wifi_IDX] ^= DIO5_BIT;
    radio.xfer(OPCODE_SET_DIO_AS_RFSWITCH, 8, 0, dioBuf);
    return DIO5wifi_read();
}

const toggle_item_t Radio::DIO5wifi_item = { _ITEM_TOGGLE, "DIO5", NULL, DIO5wifi_read, DIO5wifi_push};
const toggle_item_t Radio::DIO6wifi_item = { _ITEM_TOGGLE, "DIO6", NULL, DIO6wifi_read, DIO6wifi_push};
const toggle_item_t Radio::DIO7wifi_item = { _ITEM_TOGGLE, "DIO7", NULL, DIO7wifi_read, DIO7wifi_push};
const toggle_item_t Radio::DIO8wifi_item = { _ITEM_TOGGLE, "DIO8", NULL, DIO8wifi_read, DIO8wifi_push};
const toggle_item_t Radio::DIO10wifi_item = { _ITEM_TOGGLE, "DIO10", NULL, DIO10wifi_read, DIO10wifi_push};

const menu_t Radio::none_menu[] = {
    { {FIRST_CHIP_MENU_ROW+3,  1},          NULL, &rfswEn_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+3, 14},          NULL, &DIO10en_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+3, 21},          NULL, &DIO8en_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+3, 26},          NULL, &DIO7en_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+3, 32},          NULL, &DIO6en_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+3, 38},          NULL, &DIO5en_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+4,  1},          NULL, &rfswStbyCfg_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+4, 14},          NULL, &DIO10stby_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+4, 21},          NULL, &DIO8stby_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+4, 26},          NULL, &DIO7stby_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+4, 32},          NULL, &DIO6stby_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+4, 38},          NULL, &DIO5stby_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+5,  1},          NULL, &rfswRxCfg_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+5, 14},          NULL, &DIO10rx_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+5, 21},          NULL, &DIO8rx_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+5, 26},          NULL, &DIO7rx_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+5, 32},          NULL, &DIO6rx_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+5, 38},          NULL, &DIO5rx_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+6,  1},          NULL, &rfswTxCfg_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+6, 14},          NULL, &DIO10tx_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+6, 21},          NULL, &DIO8tx_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+6, 26},          NULL, &DIO7tx_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+6, 32},          NULL, &DIO6tx_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+6, 38},          NULL, &DIO5tx_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+7,  1},          NULL, &rfswTxHPCfg_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+7, 14},          NULL, &DIO10txhp_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+7, 21},          NULL, &DIO8txhp_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+7, 26},          NULL, &DIO7txhp_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+7, 32},          NULL, &DIO6txhp_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+7, 38},          NULL, &DIO5txhp_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+8,  1},          NULL, &rfswGnssCfg_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+8, 14},          NULL, &DIO10gnss_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+8, 21},          NULL, &DIO8gnss_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+8, 26},          NULL, &DIO7gnss_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+8, 32},          NULL, &DIO6gnss_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+8, 38},          NULL, &DIO5gnss_item, FLAG_MSGTYPE_ALL},

    { {FIRST_CHIP_MENU_ROW+9,  1},          NULL, &rfswWifiCfg_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+9, 14},          NULL, &DIO10wifi_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+9, 21},          NULL, &DIO8wifi_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+9, 26},          NULL, &DIO7wifi_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+9, 32},          NULL, &DIO6wifi_item, FLAG_MSGTYPE_ALL},
    { {FIRST_CHIP_MENU_ROW+9, 38},          NULL, &DIO5wifi_item, FLAG_MSGTYPE_ALL},

    { {LAST_CHIP_MENU_ROW,  1},          NULL, &dbg_item, FLAG_MSGTYPE_ALL},
    { {0, 0}, NULL, NULL }
};

const menu_t* Radio::get_modem_sub_menu() { return NULL; }

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

    if (pktType == PACKET_TYPE_LORA)
        return lora_menu;
    else if (pktType == PACKET_TYPE_GFSK)
        return gfsk_menu;
    else
        return none_menu;

    return NULL;
}

void Radio::setFS()
{
    radio.xfer(OPCODE_SET_FS, 0, 0, NULL);
}

void Radio::test()
{
}

void Radio::rxDone(uint8_t size, float rssi, float snr)
{
    RadioEvents->RxDone(size, rssi, snr);
}

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

void Radio::boardInit(const RadioEvents_t* e)
{
    uint8_t buf[4];

    {
        float default_ms = 62.5;
        unsigned ticks = default_ms * 32.768;
        radio.to_big_endian24(ticks, tcxo_buf+1);
    }

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

    //radio.chipModeChange = chipModeChange;
    RadioEvents = e;
    stat_t stat;
    stat.word = radio.xfer(OPCODE_GET_VERSION, 0, 0, NULL);
    stat.word = radio.xfer(0x0000, 0, 4, buf);
    if (stat.bits.cmdStatus == CMD_DAT) {
        sprintf(chip_ver, "LR1110 %02x %02x %u.%u",
            buf[0], /* silicon rev */
            buf[1], /* use case */
            buf[2], /* firmware major */
            buf[3]  /* firmware minor */
        );
    }

    initRfSwDIO();
    readChip();
}

const char* const navToHostStr[] = {
    /*  0 */ "ok",
    /*  1 */ "cmd unexpected",
    /*  2 */ "cmd not implemented",
    /*  3 */ "cmd paramters invalid",
    /*  4 */ "message sanity check",
    /*  5 */ "scanning failed",
    /*  6 */ "no time",
    /*  7 */ "no satellite detected",
    /*  8 */ "almanac too old",
    /*  9 */ "alamanac update fail: crc",
    /* 10 */ "alamanac update fail: flash integrity",
    /* 11 */ "alamanac update fail: date too old",
    /* 12 */ "alamanac update fail: not allowed",
    /* 13 */ "global almanac crc error",
    /* 14 */ "almanac version not supported",
    /* 15 */ ""
};

struct wifidr {
    const char *txt;
    float Mbps;
};

const struct wifidr wifiDatarates[] = {
    /*   0 */ { NULL, 0},
    /*   1 */ { "DBPSK", 1},
    /*   2 */ { "DQPSK", 2},
    /*   3 */ { "BPSK", 6},
    /*   4 */ { "BPSK", 9},
    /*   5 */ { "QPSK", 12},
    /*   6 */ { "QPSK", 18},
    /*   7 */ { "16-QAM", 24},
    /*   8 */ { "16-QAM", 36},
    /*   9 */ { "(9)", 0},
    /*  10 */ { "(10)", 0},
    /*  11 */ { "BPSK", 6.5},
    /*  12 */ { "QPSK", 13},
    /*  13 */ { "QPSK", 19.5},
    /*  14 */ { "16-QAM", 26},
    /*  15 */ { "16-QAM", 39},
    /*  16 */ { "(16)", 0},
    /*  17 */ { "(17)", 0},
    /*  18 */ { "(18)", 0},
    /*  19 */ { "BPSK", 7.2},
    /*  20 */ { "QPSK", 14.4},
    /*  21 */ { "QPSK", 21.7},
    /*  22 */ { "16-QAM", 28.9},
    /*  23 */ { "16-QAM", 43.3},
};


void print_wifi_result(const uint8_t *result)
{
    char out[96];
    char str[24];
    unsigned n, macStart;
    wifiType_t wt;
    wifiChanInfo_t ci;
    wt.octet = result[0];
    ci.octet = result[1];
    out[0] = 0;
    strcat(out, "802.11");
    switch (wt.bits.signal) {
        case 1: strcat(out, "b"); break;
        case 2: strcat(out, "g"); break;
        case 3: strcat(out, "n"); break;
    }
    sprintf(str, " %s %.1fMbps", wifiDatarates[wt.bits.datarate].txt, wifiDatarates[wt.bits.datarate].Mbps);
    strcat(out, str);
    strcat(out, " ");

    sprintf(str, "ch%u ", ci.bits.channelID);
    strcat(out, str);
    switch (ci.bits.channelID) {
        // table 10-5
    }
    strcat(out, " ");
    sprintf(str, "mv:%u ", ci.bits.macValidationID);
    strcat(out, str);
    switch (ci.bits.macValidationID) {
        case 1: strcat(out, "gateway"); break;
        case 2: strcat(out, "phone"); break;
        case 3: strcat(out, "?"); break;
        // table 10.8
    }

    sprintf(str, " rssi:%d ", (int8_t)result[2]);
    strcat(out, str);

    if (wifiResultFormatBasic) {
        macStart = 3;
    } else {
        macStart = 4;
    }
    for (n = 0; n < 6; n++) {
        sprintf(str, "%02x", result[n+macStart]);
        strcat(out, str);
        if (n < 5)
            strcat(out, ":");
    }
    strcat(out, " ");


    if (!wifiResultFormatBasic) {
        sprintf(str, "frameCtrl:%02x ", result[3]);
        strcat(out, str);
    }
    log_printf("%s\r\n", out);
}

bool Radio::service(int8_t statusRow)
{
    irq_t irq;
    irq.dword = radio.service();
    if (irq.dword != 0) {
        char str[94];
        sprintf(str, "irq.dword:%06lx ", irq.dword);
        if (irq.bits.TxDone)
            strcat(str, "TxDone ");
        if (irq.bits.RxDone)
            strcat(str, "RxDone ");
        if (irq.bits.PreambleDetected)
            strcat(str, "PreambleDetected ");
        if (irq.bits.SyncHeaderValid)
            strcat(str, "SyncHeaderValid ");
        if (irq.bits.HeaderErr)
            strcat(str, "HeaderErr ");
        if (irq.bits.CadDone)
            strcat(str, "CadDone ");
        if (irq.bits.CadDetected)
            strcat(str, "CadDetected ");
        if (irq.bits.Timeout)
            strcat(str, "Timeout ");
        if (irq.bits.lowBat)
            strcat(str, "lowBat ");
        if (irq.bits.FskLenError)
            strcat(str, "FskLenError ");
        if (irq.bits.FskAddrError)
            strcat(str, "FskAddrError ");
        if (irq.bits.CmdErr) {
            char txt[23];
            sprintf(txt, "\e[31mCmdErr_%04x\e[0m ", radio.err_opcode);
            strcat(str, txt);
        }
        if (irq.bits.Error) {
            strcat(str, "\e[31mError: ");
            if (radio.errorStat.bits.lf_rc_calib)
                strcat(str, "lf_rc_calib ");
            if (radio.errorStat.bits.hf_rc_calib)
                strcat(str, "hf_rc_calib ");
            if (radio.errorStat.bits.adc_calib)
                strcat(str, "adc_calib ");
            if (radio.errorStat.bits.pll_calib)
                strcat(str, "pll_calib ");
            if (radio.errorStat.bits.img_calib)
                strcat(str, "img_calib ");
            if (radio.errorStat.bits.hf_xosc_start_)
                strcat(str, "hf_xosc_start ");
            if (radio.errorStat.bits.lf_xosc_start)
                strcat(str, "lf_xosc_start ");
            if (radio.errorStat.bits.pll_lock)
                strcat(str, "pll_lock ");
            if (radio.errorStat.bits.rx_adc_offset)
                strcat(str, "rx_adc_offset ");
            strcat(str, "\e[0m");
        }

        if (irq.bits.WifiDone) {
            strcat(str, "WifiDone ");
        }
        if (irq.bits.GNSSDone) {
            strcat(str, "GNSSDone ");
        }
        log_printf("%s\r\n", str);

        /****************************/
        if (irq.bits.WifiDone) {
            stat_t stat;
            uint8_t nbResults;
            stat.word = radio.xfer(OPCODE_GET_WIFI_NB_RESULTS, 0, 0, NULL);
            stat.word = radio.xfer(0x0000, 0, 1, &nbResults);
            if (stat.bits.cmdStatus == CMD_DAT) {
                unsigned n;
                log_printf("nbResults:%u\r\n", nbResults);
                for (n = 0; n < nbResults; n++) {
                    uint8_t buf[3];
                    uint8_t resultBuf[22];
                    buf[0] = n;
                    buf[1] = 1; // number of results in this read
                    buf[2] = wifiResultFormatBasic ? 4 : 1;
                    stat.word = radio.xfer(OPCODE_WIFI_READ_RESULTS, 3, 0, buf);
                    // basic =  9byte length
                    // full  = 22byte length
                    stat.word = radio.xfer(0x0000, 0, wifiResultFormatBasic ? 9 : 22, resultBuf);
                    if (stat.bits.cmdStatus == CMD_DAT)
                        print_wifi_result(resultBuf);
                    else
                        log_printf("readResult:%s\r\n", radio.cmdStatus_toString(stat.bits.cmdStatus));
                }
            } else
                log_printf("nbResults:%s\r\n", radio.cmdStatus_toString(stat.bits.cmdStatus));
        } // ..if (irq.bits.WifiDone)

        if (irq.bits.GNSSDone) {
            uint8_t gnssResultBuf[512];
            uint8_t buf[2];
            stat_t stat;
            unsigned resultSize;
            stat.word = radio.xfer(OPCODE_GNSS_GET_RESULT_SIZE, 0, 0, NULL);
            stat.word = radio.xfer(0x0000, 0, 2, buf);
            if (stat.bits.cmdStatus == CMD_DAT) {
                resultSize = buf[1];
                resultSize <<= 8;
                resultSize |= buf[0];
                log_printf("resultSize:%u\r\n", resultSize);
                stat.word = radio.xfer(OPCODE_GNSS_READ_RESULTS, 0, 0, NULL);
                stat.word = radio.xfer(0x0000, 0, resultSize, gnssResultBuf);
                if (stat.bits.cmdStatus == CMD_DAT) {
                    unsigned i = 0, n;
                    switch (gnssResultBuf[0]) {
                        case 0: log_printf("navToHost:%s\r\n", navToHostStr[gnssResultBuf[0]]); break;
                        case 1: log_printf("navToSolver\r\n"); break;
                        case 2: log_printf("navToDMC\r\n"); break;
                        default: log_printf("nav:%u\r\n", gnssResultBuf[0]); break;
                    }
                    for (i = 0; i < resultSize; ) {
                        printf("%03x:", i);
                        for (n = 0; n < 16; n++) {
                            printf("%02x ", gnssResultBuf[i+n]);
                            if (n == 7)
                                printf(" ");
                        }
                        for (n = 0; n < 16; n++) {
                            if (n > ' ' && n < 0x7f)
                                printf("%c", gnssResultBuf[i+n]);
                            else
                                printf(".");
                            if (n == 7)
                                printf(" ");
                        }
                        printf("\r\n");
                        i += 16;
                    }
                } else
                    log_printf("resultBuf:%s\r\n", radio.cmdStatus_toString(stat.bits.cmdStatus));
            } else
                log_printf("resultSize:%s\r\n", radio.cmdStatus_toString(stat.bits.cmdStatus));
        } // ..if (irq.bits.GNSSDone)

    } // ..if (irq.dword != 0)
    return false;
}

char Radio::chip_ver[24];
const char* const Radio::chipNum_str = chip_ver;

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

menuMode_e Radio::opmode_write(unsigned sel)
{
    //stat_t stat;
    uint8_t buf[5];

    switch (sel) {
        case 0: //sleep
            {
                unsigned ticks = 0;
                buf[0] = 1;  // sleep config: dont wakeup, retain chip state
                radio.to_big_endian24(ticks, buf+1);
                /*stat.word =*/ radio.xfer(OPCODE_SET_SLEEP, 5, 0, buf);
            }
            break;
        case 1: // stby-rc
            buf[0] = 0;
            /*stat.word =*/ radio.xfer(OPCODE_SET_STANDBY, 1, 0, buf);
            break;
        case 2: // stby-xosc
            buf[0] = 1;
            /*stat.word =*/ radio.xfer(OPCODE_SET_STANDBY, 1, 0, buf);
            break;
        case 3: // fs
            setFS();
            break;
        case 4: // rx
            {
                unsigned ticks = 0xffffff; // all 1's: receive until commanded to stop
                radio.to_big_endian24(ticks, buf);
                log_printf("setRx %02x %02x %02x\r\n", buf[0], buf[1], buf[2]);
                /*stat.word =*/ radio.xfer(OPCODE_SET_RX, 3, 0, buf);
            }
            break;
        case 5: // tx
            {
                unsigned ticks = 0; // 0 = disable-tx-timeout
                radio.to_big_endian24(ticks, buf);
                log_printf("setTx %02x %02x %02x\r\n", buf[0], buf[1], buf[2]);
                /*stat.word =*/ radio.xfer(OPCODE_SET_TX, 3, 0, buf);
            }
            
            break;
    }
    return MENUMODE_REDRAW;
}

const char* const Radio::opmode_status_strs[] = {
    "SLEEP     ", // 0
    "STDBY-RC  ", // 1
    "STDBY-XOSC", // 2
    "FS        ", // 3
    "RX        ", // 4
    "TX        ", // 5
    "wifi/gnss ", // 6
    NULL
};


unsigned Radio::opmode_read(bool forWriting)
{
    uint8_t buf[4];
    stat_t stat;
    stat.word = radio.xfer(OPCODE_GET_STATUS, 4, 0, buf);
    return stat.bits.chipMode;
}

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

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

menuMode_e Radio::pktType_write(unsigned idx)
{
    //stat_t stat;
    uint8_t buf = idx;
    /*stat.word =*/ radio.xfer(OPCODE_SET_PACKET_TYPE, 1, 0, &buf);
    return MENUMODE_REINIT_MENU;
}

bool Radio::tx_dbm_write(const char* str)
{
    int dbm;
    sscanf(str, "%d", &dbm);
    tx_param_buf[0] = dbm;
    radio.xfer(OPCODE_SET_TXPARAMS, 2, 0, tx_param_buf);
    return false;
}

void Radio::tx_dbm_print()
{
    txParamsB_t txpb;   // txpb.bits.PaSel
    txParamsC_t txpc;
    radio.memRegRead(REG_ADDR_TX_PARAMS_C, 1, &txpc.dword);
    radio.memRegRead(REG_ADDR_TX_PARAMS_B, 1, &txpb.dword);
    if (txpb.bits.PaSel)
        printf("%d", txpc.bits.tx_dbm - 9);
    else
        printf("%d", txpc.bits.tx_dbm - 17);
}

const char* Radio::tx_ramp_strs[] = {
    "10  ", // 0
    "20  ", // 1
    "40  ", // 2
    "80  ", // 3
    "200 ", // 4
    "800 ", // 5
    "1700", // 6
    "3400", // 7
    NULL
};

unsigned Radio::tx_ramp_read(bool fw)
{
    txParamsC_t txpc;
    radio.memRegRead(REG_ADDR_TX_PARAMS_C, 1, &txpc.dword);
    return txpc.bits.pa_ramp_time;
}

menuMode_e Radio::tx_ramp_write(unsigned sidx)
{
    tx_param_buf[1] = sidx;
    radio.xfer(OPCODE_SET_TXPARAMS, 2, 0, tx_param_buf);
    return MENUMODE_REDRAW;
}

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


#endif /* ..SX1265_H */