wayne roberts / Mbed OS utility_sx12xx

radio_lr1110.cpp

Committer:
dudmuck
Date:
15 months ago
Revision:
15:703ca340d0fb
Parent:
13:8ce61a1897ab

File content as of revision 15:703ca340d0fb:

#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::get_rssi()
{
    stat_t stat;
    uint8_t buf;
    stat.word = radio.xfer(OPCODE_GET_RSSI_INST, 0, 0, NULL);
    stat.word = radio.xfer(0x0000, 0, 1, &buf); 
    log_printf("-%0.1f dBm\r\n", buf/2.0);   
}

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 */