wayne roberts / Mbed OS utility_sx12xx

main.cpp

Committer:
dudmuck
Date:
2021-08-24
Revision:
13:8ce61a1897ab
Parent:
9:295e37c38fb3
Child:
14:14b9e1c08bfc

File content as of revision 13:8ce61a1897ab:

#include "mbed.h"
#include "radio.h"

//#define MENU_DEBUG

BufferedSerial *_pc;
namespace mbed
{
	FileHandle *mbed_override_console(int fd)
	{
	    static BufferedSerial console(USBTX, USBRX, MBED_CONF_PLATFORM_STDIO_BAUD_RATE);
        _pc = &console;
		return &console;
	}
}

menuState_t menuState;

typedef enum {
    MSG_TYPE_PACKET = 0,
    MSG_TYPE_PER,
    MSG_TYPE_PINGPONG
} msgType_e;

// [row][column]
const menu_t* menu_table[MAX_MENU_ROWS][MAX_MENU_COLUMNS];
int8_t StopMenuCols[MAX_MENU_ROWS];

#define SCROLLING_ROWS      20

struct _cp_ {
    uint8_t row;
    uint8_t tableCol;
} curpos;

uint8_t entry_buf_idx;
char entry_buf[64];

msgType_e msg_type;

const uint8_t PingMsg[] = "PING";
const uint8_t PongMsg[] = "PONG";
const uint8_t PerMsg[]  = "PER";

volatile struct _flags_ {
    uint8_t ping_master     : 1; // 0
    uint8_t send_ping       : 1; // 1
    uint8_t send_pong       : 1; // 2
    uint8_t pingpongEnable  : 1; // 3
    uint8_t do_next_tx      : 1; // 4
} flags;

uint8_t botRow;
int regAddr = -1;

struct {
    float start_MHz;
    float step_MHz;
    float stop_MHz;
    unsigned dwell_ms;
    float MHz;
    Ticker ticker;
    bool ticking;
    bool tick;
} cf_sweep;


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

    printf("\r\n");
}

unsigned lfsr;
#define LFSR_INIT       0x1ff

uint8_t get_pn9_byte()
{
    uint8_t ret = 0;
    int xor_out;

    xor_out = ((lfsr >> 5) & 0xf) ^ (lfsr & 0xf);   // four bits at a time
    lfsr = (lfsr >> 4) | (xor_out << 5);    // four bits at a time

    ret |= (lfsr >> 5) & 0x0f;

    xor_out = ((lfsr >> 5) & 0xf) ^ (lfsr & 0xf);   // four bits at a time
    lfsr = (lfsr >> 4) | (xor_out << 5);    // four bits at a time

    ret |= ((lfsr >> 1) & 0xf0);

    return ret;
}

void hw_reset_push()
{
    Radio::hw_reset();

    Radio::readChip();
    menuState.mode = MENUMODE_REINIT_MENU;
}

const button_item_t hw_reset_item = { _ITEM_BUTTON, "hw_reset", hw_reset_push };

void clearIrqs_push()
{
    Radio::clearIrqFlags();
}

const button_item_t clearirqs_item = { _ITEM_BUTTON, "clearIrqs", clearIrqs_push };

const dropdown_item_t pktType_item = { _ITEM_DROPDOWN, Radio::pktType_strs, Radio::pktType_strs, Radio::pktType_read, Radio::pktType_write};


unsigned msgType_read(bool fw)
{
    return msg_type;
}

menuMode_e msgType_write(unsigned widx)
{
    msg_type = (msgType_e)widx;

    if (msg_type == MSG_TYPE_PER) {
        flags.pingpongEnable = 0;
        if (Radio::get_payload_length() < 7)
            Radio::set_payload_length(7);
    } else if (msg_type == MSG_TYPE_PINGPONG) {
        if (Radio::get_payload_length() != 12)
            Radio::set_payload_length(12);
    } else if (msg_type == MSG_TYPE_PACKET) {
        flags.pingpongEnable = 0;
    }

    return MENUMODE_REINIT_MENU;
}

const char* const msgType_strs[] = {
    "PACKET ",
    "PER    ",
    "PINGPONG",
    NULL
};

const dropdown_item_t msgType_item = { _ITEM_DROPDOWN, msgType_strs, msgType_strs, msgType_read, msgType_write};

void sweep_start_print()
{
    printf("%.3fMHz", cf_sweep.start_MHz);
}

bool sweep_start_write(const char* valStr)
{
    sscanf(valStr, "%f", &cf_sweep.start_MHz);
    return false;
}

void sweep_step_print()
{
    printf("%.3fMHz", cf_sweep.step_MHz);
}

bool sweep_step_write(const char* valStr)
{
    sscanf(valStr, "%f", &cf_sweep.step_MHz);
    return false;
}

void sweep_stop_print()
{
    printf("%.3fMHz", cf_sweep.stop_MHz);
}

bool sweep_stop_write(const char* valStr)
{
    sscanf(valStr, "%f", &cf_sweep.stop_MHz);
    return false;
}

void sweep_dwell_print()
{
    printf("%ums", cf_sweep.dwell_ms);
}

bool sweep_dwell_write(const char* valStr)
{
    sscanf(valStr, "%u", &cf_sweep.dwell_ms);
    return false;
}

const value_item_t tx_payload_length_item = { _ITEM_VALUE, 5, Radio::tx_payload_length_print, Radio::tx_payload_length_write};

void pn9_push()
{
    uint8_t i, len = Radio::get_payload_length();
    for (i = 0; i < len; i++)
        Radio::radio.tx_buf[i] = get_pn9_byte();
}

const button_item_t tx_payload_pn9_item  = { _ITEM_BUTTON, "PN9", pn9_push };

void regAddr_print()
{
    if (regAddr != -1)
        printf("%x", regAddr);
}

bool regAddr_write(const char* txt)
{
    sscanf(txt, "%x", &regAddr);
    return false;
}

const value_item_t regAddr_item = { _ITEM_VALUE, 5, regAddr_print, regAddr_write};

void regValue_print()
{
    if (regAddr != -1) {
        printf("%x", Radio::read_register(regAddr));
    }
}

bool regValue_write(const char* txt)
{
    unsigned val;
    sscanf(txt, "%x", &val);
    if (regAddr != -1) {
        Radio::write_register(regAddr, val);
    }
    return false;
}

const value_item_t regValue_item = { _ITEM_VALUE, 5, regValue_print, regValue_write};

void tx_payload_print()
{
    uint8_t i, len = Radio::get_payload_length();
    for (i = 0; i < len; i++)
        printf("%02x ", Radio::radio.tx_buf[i]);
}

bool tx_payload_write(const char* txt)
{
    unsigned o, i = 0, pktidx = 0;
    while (i < entry_buf_idx) {
        sscanf(entry_buf+i, "%02x", &o);
        printf("%02x ", o);
        i += 2;
        Radio::radio.tx_buf[pktidx++] = o;
        while (entry_buf[i] == ' ' && i < entry_buf_idx)
            i++;
    }
    log_printf("set payload len %u\r\n", pktidx);
    Radio::set_payload_length(pktidx);
    return true;
}

const value_item_t tx_payload_item = { _ITEM_VALUE, 80, tx_payload_print, tx_payload_write};

void txpkt_push()
{
    Radio::txPkt();
}

const button_item_t tx_pkt_item  = { _ITEM_BUTTON, "TXPKT", txpkt_push };

void rxpkt_push()
{
    Radio::Rx();
    //yyy menuState.mode = MENUMODE_REDRAW;
}
const button_item_t rx_pkt_item  = { _ITEM_BUTTON, "RXPKT", rxpkt_push };

const dropdown_item_t opmode_item = { _ITEM_DROPDOWN, Radio::opmode_status_strs, Radio::opmode_select_strs, Radio::opmode_read, Radio::opmode_write };

void tx_carrier_push()
{
    Radio::tx_carrier();
}
const button_item_t tx_carrier_item = { _ITEM_BUTTON, "TX_CARRIER", tx_carrier_push };

void tx_preamble_push()
{
    Radio::tx_preamble();
}
const button_item_t tx_preamble_item = { _ITEM_BUTTON, "TX_PREAMBLE", tx_preamble_push };

const value_item_t sweep_start_item = { _ITEM_VALUE, 5, sweep_start_print, sweep_start_write};
const value_item_t sweep_step_item = { _ITEM_VALUE, 5, sweep_step_print, sweep_step_write};
const value_item_t sweep_stop_item = { _ITEM_VALUE, 5, sweep_stop_print, sweep_stop_write};
const value_item_t sweep_dwell_item = { _ITEM_VALUE, 5, sweep_dwell_print, sweep_dwell_write};

const menu_t msg_pkt_menu[] = {
    { {LAST_CHIP_MENU_ROW+1,  1}, "tx payload length:", &tx_payload_length_item, FLAG_MSGTYPE_PKT },
    { {LAST_CHIP_MENU_ROW+1, 25},                 NULL,    &tx_payload_pn9_item, FLAG_MSGTYPE_PKT, &tx_payload_item },
    { {LAST_CHIP_MENU_ROW+1, 40},           "regAddr:",           &regAddr_item, FLAG_MSGTYPE_PKT, &regValue_item },
    { {LAST_CHIP_MENU_ROW+1, 53},          "regValue:",          &regValue_item, FLAG_MSGTYPE_PKT, },
    { {LAST_CHIP_MENU_ROW+2,  1},                 NULL,        &tx_payload_item, FLAG_MSGTYPE_PKT },
    { {LAST_CHIP_MENU_ROW+3,  1},                 NULL,            &tx_pkt_item, FLAG_MSGTYPE_PKT, &opmode_item },
    { {LAST_CHIP_MENU_ROW+3, 10},                 NULL,            &rx_pkt_item, FLAG_MSGTYPE_PKT },
    { {LAST_CHIP_MENU_ROW+3, 20},                 NULL,        &tx_carrier_item, FLAG_MSGTYPE_PKT, &opmode_item },
    { {LAST_CHIP_MENU_ROW+3, 45},                 NULL,       &tx_preamble_item, FLAG_MSGTYPE_PKT, &opmode_item },
    { {LAST_CHIP_MENU_ROW+4,  1},    "cf sweep start:",       &sweep_start_item, FLAG_MSGTYPE_PKT },
    { {LAST_CHIP_MENU_ROW+4, 28},              "step:",        &sweep_step_item, FLAG_MSGTYPE_PKT },
    { {LAST_CHIP_MENU_ROW+4, 45},              "stop:",        &sweep_stop_item, FLAG_MSGTYPE_PKT },
    { {LAST_CHIP_MENU_ROW+4, 62},          "dwell_ms:",       &sweep_dwell_item, FLAG_MSGTYPE_PKT },

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

unsigned tx_ipd_ms;
Timeout mbedTimeout;
unsigned MaxNumPacket;
unsigned CntPacketTx;
unsigned PacketRxSequencePrev;
unsigned CntPacketRxKO;
unsigned CntPacketRxOK;
unsigned RxTimeOutCount;
unsigned receivedCntPacket;

void do_next_tx ()
{
    Radio::radio.tx_buf[0] = CntPacketTx >> 24;
    Radio::radio.tx_buf[1] = CntPacketTx >> 16;
    Radio::radio.tx_buf[2] = CntPacketTx >> 8;
    Radio::radio.tx_buf[3] = CntPacketTx;

    Radio::radio.tx_buf[4] = PerMsg[0];
    Radio::radio.tx_buf[5] = PerMsg[1];
    Radio::radio.tx_buf[6] = PerMsg[2];

    Radio::txPkt();
}

void next_tx_callback()
{
    flags.do_next_tx = 1;
}


void pertx_push()
{
    CntPacketTx = 1;    // PacketRxSequencePrev initialized to 0 on receiver

    log_printf("do perTx\r\n");

    next_tx_callback();
}


const button_item_t pertx_item = { _ITEM_BUTTON, "PERTX", pertx_push };

void tx_ipd_print()
{
    printf("%u", tx_ipd_ms);
}

bool tx_ipd_write(const char* valStr)
{
    sscanf(valStr, "%u", &tx_ipd_ms);
    return false;
}

value_item_t per_ipd_item = { _ITEM_VALUE, 4, tx_ipd_print, tx_ipd_write };

void numpkts_print()
{
    printf("%u", MaxNumPacket);
}

bool numpkts_write(const char* valStr)
{
    sscanf(valStr, "%u", &MaxNumPacket);
    return false;
}

value_item_t per_numpkts_item = { _ITEM_VALUE, 6, numpkts_print, numpkts_write };

void perrx_push()
{
    PacketRxSequencePrev = 0;
    CntPacketRxKO = 0;
    CntPacketRxOK = 0;
    RxTimeOutCount = 0;

    Radio::Rx();
}

const button_item_t perrx_item = { _ITEM_BUTTON, "PERRX", perrx_push };

void per_reset_push()
{
    PacketRxSequencePrev = 0;
    CntPacketRxKO = 0;
    CntPacketRxOK = 0;
    RxTimeOutCount = 0;
}

const button_item_t per_clear_item = { _ITEM_BUTTON, "resetCount", per_reset_push };

const menu_t msg_per_menu[] = {
    { {LAST_CHIP_MENU_ROW+3,  1},          NULL,       &pertx_item, FLAG_MSGTYPE_PER },
    { {LAST_CHIP_MENU_ROW+3,  10}, "TX IPD ms:",     &per_ipd_item, FLAG_MSGTYPE_PER },
    { {LAST_CHIP_MENU_ROW+3,  26},   "numPkts:", &per_numpkts_item, FLAG_MSGTYPE_PER },
    { {LAST_CHIP_MENU_ROW+3,  40},         NULL,       &perrx_item, FLAG_MSGTYPE_PER, &opmode_item },
    { {LAST_CHIP_MENU_ROW+3,  50},         NULL,   &per_clear_item, FLAG_MSGTYPE_PER, &opmode_item },
    { {0, 0}, NULL, NULL }
};

void SendPong()
{
    unsigned failCnt = CntPacketRxKO + RxTimeOutCount;

    /* ping slave tx */
    log_printf("ACK PKT%u\r\n", receivedCntPacket);

    Radio::radio.tx_buf[ 0] = receivedCntPacket >> 24;
    Radio::radio.tx_buf[ 1] = receivedCntPacket >> 16;
    Radio::radio.tx_buf[ 2] = receivedCntPacket >> 8;
    Radio::radio.tx_buf[ 3] = receivedCntPacket;
    Radio::radio.tx_buf[ 4] = failCnt >> 24;
    Radio::radio.tx_buf[ 5] = failCnt >> 16;
    Radio::radio.tx_buf[ 6] = failCnt >> 8;
    Radio::radio.tx_buf[ 7] = failCnt;
    Radio::radio.tx_buf[ 8] = PongMsg[0];
    Radio::radio.tx_buf[ 9] = PongMsg[1];
    Radio::radio.tx_buf[10] = PongMsg[2];
    Radio::radio.tx_buf[11] = PongMsg[3];

    Radio::txPkt();
}

void SendPing()
{
    /* ping master tx */

    log_printf("MASTER > PING PKT%u\r\n", CntPacketTx);
    Radio::radio.tx_buf[0] = CntPacketTx >> 24;
    Radio::radio.tx_buf[1] = CntPacketTx >> 16;
    Radio::radio.tx_buf[2] = CntPacketTx >> 8;
    Radio::radio.tx_buf[3] = CntPacketTx;
    Radio::radio.tx_buf[4] = PingMsg[0];
    Radio::radio.tx_buf[5] = PingMsg[1];
    Radio::radio.tx_buf[6] = PingMsg[2];
    Radio::radio.tx_buf[7] = PingMsg[3];

    Radio::txPkt();
}

void
pingpong_start_push()
{
    CntPacketTx = 1;
    CntPacketRxKO = 0;
    CntPacketRxOK = 0;
    RxTimeOutCount = 0;
    receivedCntPacket = 0;

    flags.send_ping = 1;

    flags.ping_master = 1;

    flags.pingpongEnable = 1;
    log_printf("ping start\r\n");
}

const button_item_t pingpong_start_item = { _ITEM_BUTTON, "START", pingpong_start_push };

void
pingpong_stop_push ()
{
    flags.pingpongEnable = 0;
}

const button_item_t pingpong_stop_item = { _ITEM_BUTTON, "STOP", pingpong_stop_push };

const menu_t msg_pingpong_menu[] = {
    { {LAST_CHIP_MENU_ROW+3,  1}, NULL, &pingpong_start_item, FLAG_MSGTYPE_PING },
    { {LAST_CHIP_MENU_ROW+3, 15}, NULL,  &pingpong_stop_item, FLAG_MSGTYPE_PING },
    { {0, 0}, NULL, NULL }
};

const menu_t* get_msg_menu()
{
    switch (msg_type) {
        case MSG_TYPE_PACKET:
            return msg_pkt_menu;
        case MSG_TYPE_PER:
            return msg_per_menu;
        case MSG_TYPE_PINGPONG:
            return msg_pingpong_menu;
    }
    return NULL;
}

void frf_print()
{
    float MHz;
#ifdef SX127x_H
    MHz = Radio::radio.get_frf_MHz();
#else
    MHz = Radio::radio.getMHz();
#endif
    printf("%.3fMHz", MHz);
}

bool frf_write(const char* valStr)
{
    float MHz;
    sscanf(valStr, "%f", &MHz);
#ifdef SX127x_H
    Radio::radio.set_frf_MHz(MHz);
#else
    Radio::radio.setMHz(MHz);
#endif
    return false;
}

const value_item_t frf_item = { _ITEM_VALUE, 14, frf_print, frf_write };

const value_item_t Radio::tx_dbm_item = { _ITEM_VALUE, 7, Radio::tx_dbm_print, Radio::tx_dbm_write };

const dropdown_item_t tx_ramp_item = { _ITEM_DROPDOWN, Radio::tx_ramp_strs, Radio::tx_ramp_strs, Radio::tx_ramp_read, Radio::tx_ramp_write };

const button_item_t chipNum_item = { _ITEM_BUTTON, Radio::chipNum_str, NULL};

void botrow_print()
{
    printf("%u", botRow);
}

bool botrow_write(const char* str)
{
    unsigned n;
    sscanf(str, "%u", &n);
    botRow = n;

    printf("\e[%u;%ur", MAX_MENU_ROWS, botRow); // set scrolling region

    return false;
}

const value_item_t bottomRow_item = { _ITEM_VALUE, 4, botrow_print, botrow_write };

const menu_t common_menu[] = {
    {  {1, 1},          NULL,      &hw_reset_item, FLAG_MSGTYPE_ALL },
    { {1, 13}, "packetType:",       &pktType_item, FLAG_MSGTYPE_ALL },
    { {1, 33},          NULL,       &msgType_item, FLAG_MSGTYPE_ALL },
    { {1, 43},          NULL,       &chipNum_item, FLAG_MSGTYPE_ALL },
    { {1, 61},  "bottomRow:",     &bottomRow_item, FLAG_MSGTYPE_ALL },
    { {2, 1},           NULL,           &frf_item, FLAG_MSGTYPE_ALL },
    { {2, 15},     "TX dBm:", &Radio::tx_dbm_item, FLAG_MSGTYPE_ALL },
    { {2, 30},    "ramp us:",       &tx_ramp_item, FLAG_MSGTYPE_ALL },
    { {2, 45},          NULL,     &clearirqs_item, FLAG_MSGTYPE_ALL },
    { {2, 55},     "opmode:",        &opmode_item, FLAG_MSGTYPE_ALL },


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

bool
is_menu_item_changable(uint8_t table_row, uint8_t table_col)
{
    const menu_t* m = menu_table[table_row][table_col];
    const dropdown_item_t* di = (const dropdown_item_t*)m->itemPtr;

    switch (msg_type) {
        case MSG_TYPE_PACKET:
            if (m->flags & FLAG_MSGTYPE_PKT)
                break;
            else
                return false;
        case MSG_TYPE_PER:
            if (m->flags & FLAG_MSGTYPE_PER)
                break;
            else
                return false;
        case MSG_TYPE_PINGPONG:
            if (m->flags & FLAG_MSGTYPE_PING)
                break;
            else
                return false;
    }

    if (di->itemType == _ITEM_DROPDOWN) {
        if (di->selectable_strs == NULL || di->selectable_strs[0] == NULL) {
            log_printf("NULLstrs%u,%u\r\n", table_row, table_col);
            return false;
        }
    } else if (di->itemType == _ITEM_VALUE) {
        const value_item_t* vi = (const value_item_t*)m->itemPtr;
        if (vi->write == NULL)
            return false;
    } else if (di->itemType == _ITEM_BUTTON) {
        const button_item_t* bi = (const button_item_t*)m->itemPtr;
        if (bi->push == NULL)
            return false;
    } else if (di->itemType == _ITEM_TOGGLE) {
        const toggle_item_t * ti = (const toggle_item_t *)m->itemPtr;
        if (ti->push == NULL)
            return false;
    }

    return true;
} // ..is_menu_item_changable()

void read_menu_item(const menu_t* m, bool selected)
{
    uint8_t valCol;
    const dropdown_item_t* di = (const dropdown_item_t*)m->itemPtr;

    switch (msg_type) {
        case MSG_TYPE_PACKET:
            if (m->flags & FLAG_MSGTYPE_PKT)
                break;
            else
                return;
        case MSG_TYPE_PER:
            if (m->flags & FLAG_MSGTYPE_PER)
                break;
            else
                return;
        case MSG_TYPE_PINGPONG:
            if (m->flags & FLAG_MSGTYPE_PING)
                break;
            else
                return;
    }

    printf("\e[%u;%uf", m->pos.row, m->pos.col);  // set (force) cursor to row;column
    valCol = m->pos.col;
    if (m->label) {
        printf("%s", m->label);
        valCol += strlen(m->label);
    }
    if (di->itemType == _ITEM_DROPDOWN) {
        if (di->read && di->printed_strs) {
            uint8_t ridx = di->read(false);
            if (selected)
                printf("\e[7m");
            printf("%s", di->printed_strs[ridx]);
            if (selected)
                printf("\e[0m");
        } else if (di->printed_strs) {
            printf("%s", di->printed_strs[0]);
        }
    } else if (di->itemType == _ITEM_VALUE) {
        const value_item_t* vi = (const value_item_t*)m->itemPtr;
        if (vi->print) {
            for (unsigned n = 0; n < vi->width; n++)
                printf(" ");

            printf("\e[%u;%uf", m->pos.row, valCol);  // set (force) cursor to row;column
            if (selected)
                printf("\e[7m");
            vi->print();
            if (selected)
                printf("\e[0m");
        }
    } else if (di->itemType == _ITEM_BUTTON) {
        const button_item_t* bi = (const button_item_t*)m->itemPtr;
        if (bi->label) {
            if (selected)
                printf("\e[7m%s\e[0m", bi->label);
            else
                printf("%s", bi->label);
        }
    } else if (di->itemType == _ITEM_TOGGLE) {
        const toggle_item_t* ti = (const toggle_item_t *)m->itemPtr;
        bool on = ti->read();
        if (ti->label1) {
            const char* const cptr = on ? ti->label1 : ti->label0;
            if (selected)
                printf("\e[7m%s\e[0m", cptr);
            else
                printf("%s", cptr);
        } else {
            if (on)
                printf("\e[1m");
            if (selected)
                printf("\e[7m");

            printf("%s", ti->label0);

            if (selected || on)
                printf("\e[0m");
        }
    }
} // ..read_menu_item()

void draw_menu()
{
    unsigned table_row;

    for (table_row = 0; table_row < MAX_MENU_ROWS; table_row++) {
        int table_col;
        for (table_col = 0; table_col < StopMenuCols[table_row]; table_col++) {
            read_menu_item(menu_table[table_row][table_col], false);
        } // ..table column iterator
    } // ..table row iterator

    read_menu_item(menu_table[curpos.row][curpos.tableCol], true);

} // ..draw_menu()

typedef struct {
    int row;
    int col;
} tablexy_t;

void
menu_init_(const menu_t* in, tablexy_t* tc)
{
    unsigned n;

    for (n = 0; in[n].pos.row > 0; n++) {
        const menu_t* m = &in[n];
        if (tc->row != m->pos.row - 1) {
            tc->row = m->pos.row - 1;
            tc->col = 0;
        } else
            tc->col++;

        menu_table[tc->row][tc->col] = m;
#ifdef MENU_DEBUG
        printf("table:%u,%u ", tc->row, tc->col);
        printf(" %d<%d? ", StopMenuCols[tc->row], tc->col);
#endif /* MENU_DEBUG */
        if (StopMenuCols[tc->row] < tc->col)
            StopMenuCols[tc->row] = tc->col;
#ifdef MENU_DEBUG
        printf("{%u %u}", tc->row, tc->col);
        printf("in:%p[%u] screen:%u,%u ", in, n, m->pos.row, m->pos.col);
        printf("pos@%p ", &m->pos);
        //printf(" loc:%p ", &in[n].itemPtr);
        if (in[n].itemPtr) {
            const dropdown_item_t* di = (const dropdown_item_t*)m->itemPtr;
            printf(" itemPtr:%p type:%02x ", di, di->itemType);
        }
        printf("stopMenuCols[%u]: %d ", tc->row, StopMenuCols[tc->row]);
        if (m->label)
            printf("label:%s", m->label);
        else
            printf("noLabel");
        printf("\r\n");
#endif /* MENU_DEBUG */
    }
#ifdef MENU_DEBUG
    printf("hit key:");
    _pc->getc();
#endif /* MENU_DEBUG */

}

void navigate_dropdown(uint8_t ch)
{
    unsigned n;
    const menu_t* m = menuState.sm;
    const dropdown_item_t* di = (const dropdown_item_t*)m->itemPtr;

    switch (ch) {
        case 'A':   // cursor UP
            if (menuState.sel_idx > 0) {
                menuState.sel_idx--;
            }
            break;
        case 'B':   // cursor DOWN
            if (di->selectable_strs[menuState.sel_idx+1] != NULL)
                menuState.sel_idx++;
            break;
    } // ..switch (ch)

    for (n = 0; di->selectable_strs[n] != NULL; n++) {
        printf("\e[%u;%uf", m->pos.row+n, menuState.dropdown_col);
        if (n == menuState.sel_idx)
            printf("\e[7m");
        printf("%s", di->selectable_strs[n]);
        if (n == menuState.sel_idx)
            printf("\e[0m");
    }
    printf("\e[%u;%uf", m->pos.row + menuState.sel_idx, menuState.dropdown_col + strlen(di->selectable_strs[menuState.sel_idx]));
}

bool is_item_selectable(const menu_t* m)
{
    const dropdown_item_t* di = (const dropdown_item_t*)m->itemPtr;

    if (di->itemType == _ITEM_BUTTON) {
        const button_item_t* bi = (const button_item_t*)m->itemPtr;
        if (bi->push == NULL)
            return false;
    } else if (di->itemType == _ITEM_TOGGLE) {
        const toggle_item_t* ti = (const toggle_item_t*)m->itemPtr;
        if (ti->push == NULL)
            return false;
    }

    return true;
}

void navigate_menu(uint8_t ch)
{
    read_menu_item(menu_table[curpos.row][curpos.tableCol], false);

    switch (ch) {
        case 'A':   // cursor UP
            if (curpos.row == 0)
                break;

            {   // find previous row up with column
                int8_t row;
                for (row = curpos.row - 1; row >= 0; row--) {
                    if (StopMenuCols[row] > -1) {
                        curpos.row = row;
                        break;
                    }
                }
                if (row == 0 && StopMenuCols[0] < 0)
                    break;  // nothing found
            }

            if (curpos.tableCol >= StopMenuCols[curpos.row]) {
                curpos.tableCol = StopMenuCols[curpos.row]-1;
            }

            break;
        case 'B':   // cursor DOWN
            if (curpos.row >= MAX_MENU_ROWS)
                break;

            {   // find next row down with column
                uint8_t row;
                for (row = curpos.row + 1; row < MAX_MENU_ROWS; row++) {
                    if (StopMenuCols[row] != -1) {
                        curpos.row = row;
                        break;
                    }
                }
                if (row == MAX_MENU_ROWS-1 && StopMenuCols[row] == -1)
                    break;  // nothing found
            }

            if (curpos.tableCol >= StopMenuCols[curpos.row]) {
                curpos.tableCol = StopMenuCols[curpos.row]-1;
            }


            break;
        case 'C':    // cursor LEFT
            if (curpos.tableCol >= StopMenuCols[curpos.row]-1)
                break;

            { // find next row left with editable
                uint8_t tcol;
                for (tcol = curpos.tableCol + 1; tcol < StopMenuCols[curpos.row]; tcol++) {
                    if (is_menu_item_changable(curpos.row, tcol)) {
                        curpos.tableCol = tcol;
                        break;
                    }
                }
            }

            break;
        case 'D':   // cursor RIGHT
            if (curpos.tableCol == 0)
                break;

            {
                int8_t tcol;
                for (tcol = curpos.tableCol - 1;  tcol >= 0; tcol--) {
                    if (is_menu_item_changable(curpos.row, tcol)) {
                        curpos.tableCol = tcol;
                        break;
                    }
                }
            }

            break;
        default:
            //printf("unhancled-csi:%02x\eE", ch);
            break;
    } // ..switch (ch)

    if (!is_item_selectable(menu_table[curpos.row][curpos.tableCol])) {
        int c;
        for (c = 0; c < StopMenuCols[curpos.row]; c++) {
            if (is_item_selectable(menu_table[curpos.row][c])) {
                curpos.tableCol = c;
                break;
            }
        }
        if (c == StopMenuCols[curpos.row])
            return;
    }

#ifdef MENU_DEBUG
    log_printf("table:%u,%u screen:%u,%u      \r\n", curpos.row, curpos.tableCol, 
        menu_table[curpos.row][curpos.tableCol]->pos.row,
        menu_table[curpos.row][curpos.tableCol]->pos.col
    );
#endif /* MENU_DEBUG */

    read_menu_item(menu_table[curpos.row][curpos.tableCol], true);
} // ..navigate_menu


bool is_sweep_menu_item(const void* const itemPtr)
{
    if (itemPtr == &sweep_start_item ||
        itemPtr == &sweep_stop_item ||
        itemPtr == &sweep_dwell_item ||
        itemPtr == &tx_carrier_item ||
        itemPtr == &tx_preamble_item
    )
        return true;
    else
        return false;
}

void commit_menu_item_change()
{
    const menu_t* m = menu_table[curpos.row][curpos.tableCol];
    const dropdown_item_t* di = (const dropdown_item_t*)m->itemPtr;

    if (di->itemType == _ITEM_DROPDOWN) {
        menuState.mode = di->write(menuState.sel_idx);

        printf("\e[%u;%uf", m->pos.row, m->pos.col-2);
    } else if (di->itemType == _ITEM_VALUE) {
        const value_item_t* vi = (const value_item_t*)m->itemPtr;
        /* commit value entry */
        if (vi->write) {
            if (vi->write(entry_buf))
                menuState.mode = MENUMODE_REDRAW;
            else
                menuState.mode = MENUMODE_NONE;
        } else
            menuState.mode = MENUMODE_NONE;

        if (menuState.mode == MENUMODE_NONE) {
            read_menu_item(menu_table[curpos.row][curpos.tableCol], true);
        }
    }

} // ..commit_menu_item_change()

void refresh_item_in_table(const void* item)
{
    unsigned table_row;

    if (item == NULL)
        return;

    for (table_row = 0; table_row < MAX_MENU_ROWS; table_row++) {
        int table_col;
        for (table_col = 0; table_col < StopMenuCols[table_row]; table_col++) {
            //log_printf("%u %u %p\r\n", table_row, table_col, menu_table[table_row][table_col]->itemPtr);
            if (item == menu_table[table_row][table_col]->itemPtr) {
                read_menu_item(menu_table[table_row][table_col], false);
                return;
            }
        }
    }
}

void
start_value_entry(const menu_t* m)
{
    const value_item_t* vi = (const value_item_t*)m->itemPtr;
    uint8_t col = m->pos.col;

    if (m->label)
        col += strlen(m->label);

    printf("\e[%u;%uf", m->pos.row, col);
    for (unsigned i = 0; i < vi->width; i++)
        printf(" ");   // clear displayed value for user entry

    printf("\e[%u;%uf", m->pos.row, col);
    menuState.mode = MENUMODE_ENTRY;
    entry_buf_idx = 0;
}

void start_menu_item_change()
{
    const menu_t* m = menu_table[curpos.row][curpos.tableCol];
    const dropdown_item_t* di = (const dropdown_item_t*)m->itemPtr;
    bool checkRefresh = false;

    if (di->itemType == _ITEM_DROPDOWN && di->selectable_strs) {
        menuState.dropdown_col = m->pos.col;
        unsigned n, sidx = 0;
        /* start dropdown */
        if (di->read)
            sidx = di->read(true);

        if (m->label)
            menuState.dropdown_col += strlen(m->label);

        for (n = 0; di->selectable_strs[n] != NULL; n++) {
            uint8_t col = menuState.dropdown_col;
            bool leftPad = false;
            if (col > 3 && n > 0) { // dropdown left side padding
                col -= 2;
                leftPad = true;
            }
            printf("\e[%u;%uf", m->pos.row+n, col);
            if (leftPad ) {
                printf("  ");
            }
            if (n == sidx)
                printf("\e[7m");
            printf("%s", di->selectable_strs[n]);
            if (n == sidx)
                printf("\e[0m");
            printf("  ");   // right side padding
        }
        printf("\e[%u;%uf", m->pos.row, menuState.dropdown_col-2);

        menuState.mode = MENUMODE_DROPDOWN;
        menuState.sel_idx = sidx;
        menuState.sm = m;
    } else if (di->itemType == _ITEM_VALUE) {
        /* start value entry */
        start_value_entry(m);
    } else if (di->itemType == _ITEM_BUTTON) {
        const button_item_t* bi = (const button_item_t*)m->itemPtr;
        if (bi->push) {
            bi->push();
            checkRefresh = true;
        }
    } else if (di->itemType == _ITEM_TOGGLE) {
        const toggle_item_t* ti = (const toggle_item_t*)m->itemPtr;
        if (ti->push) {
            bool on = ti->push();
            uint8_t col = m->pos.col;

            if (m->label)
                col += strlen(m->label);

            printf("\e[%u;%uf", m->pos.row, col);
            if (ti->label1) {
                printf("\e[7m%s\e[0m", on ? ti->label1 : ti->label0);
            } else {
                if (on)
                    printf("\e[1;7m%s\e[0m", ti->label0);
                else
                    printf("\e[7m%s\e[0m", ti->label0);
            }
            checkRefresh = true;
        }
    }

    if (checkRefresh) {
        if (m->refreshReadItem) {
            refresh_item_in_table(m->refreshReadItem);  // read associated
            read_menu_item(m, true);   // restore cursor
        }
    }

} // ..start_menu_item_change()

void full_menu_init()
{
    unsigned n;
    const menu_t *m;
    tablexy_t txy;

    txy.row = INT_MAX;
    txy.col = 0;

    for (n = 0; n < MAX_MENU_ROWS; n++) {
        StopMenuCols[n] = -1;
    }

    menu_init_(common_menu, &txy);

    menu_init_(Radio::common_menu, &txy);

    m = Radio::get_modem_menu();
    if (m == NULL) {
        log_printf("NULL-modemMenu\r\n");
        for (;;) asm("nop");
    }
#ifdef MENU_DEBUG
    printf("modemmenuInit\r\n");
#endif
    menu_init_(m, &txy);

    m = Radio::get_modem_sub_menu();
    if (m) {
#ifdef MENU_DEBUG
        printf("modemsubmenuInit\r\n");
#endif
        menu_init_(m, &txy);
    }
#ifdef MENU_DEBUG
    else
        printf("no-modemsubmenu\r\n");
#endif

    m = get_msg_menu();
    if (m == NULL) {
        log_printf("NULL-msgMenu\r\n");
        for (;;) asm("nop");
    }
    menu_init_(m, &txy);

    for (n = 0; n < MAX_MENU_ROWS; n++) {
        if (StopMenuCols[n] != -1)
            StopMenuCols[n]++;
    }
}

bool ishexchar(char ch)
{
    if (((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) || (ch >= '0' && ch <= '9'))
        return true;
    else
        return false;
}

enum _urx_ {
    URX_STATE_NONE = 0,
    URX_STATE_ESCAPE,
    URX_STATE_CSI,
} uart_rx_state;

Timeout uartRxTimeout;

void uart_rx_timeout()
{
    /* escape by itself: abort change on item */
    menuState.mode = MENUMODE_REDRAW;

    uart_rx_state = URX_STATE_NONE;
}

void sweeper()
{
    if (cf_sweep.ticking) {
        cf_sweep.tick = true;
    }
}

void sweep_service()
{
    const menu_t* m = menu_table[curpos.row][curpos.tableCol];

    if (is_sweep_menu_item(m->itemPtr)) {
        if (cf_sweep.start_MHz != 0 && cf_sweep.stop_MHz > cf_sweep.start_MHz && cf_sweep.dwell_ms > 0) {
            std::chrono::microseconds us = std::chrono::microseconds(cf_sweep.dwell_ms * 1000);
            cf_sweep.MHz = cf_sweep.start_MHz;
            cf_sweep.ticker.attach(sweeper, us);
            cf_sweep.ticking = true;
        }
    } else {
        if (cf_sweep.ticking) {
            cf_sweep.ticker.detach();
            cf_sweep.ticking = false;
        }
    }
}

void serial_callback()
{
    char serial_write_buf[4];
    unsigned char serial_write_buf_idx = 0;
    //char ch = pc.getc();
    char ch;
    _pc->read(&ch, 1);

    switch (uart_rx_state) {
        case URX_STATE_NONE:
            if (ch == 0x1b) {
                if (menuState.mode == MENUMODE_ENTRY) {
                    /* abort entry mode */
                    menuState.mode = MENUMODE_NONE;
                    read_menu_item(menu_table[curpos.row][curpos.tableCol], true);
                } else {
                    uart_rx_state = URX_STATE_ESCAPE;
                    if (menuState.mode != MENUMODE_NONE) {
                        /* is this escape by itself, user wants to abort? */
                        uartRxTimeout.attach(uart_rx_timeout, 30ms);
                    }
                }
            } else if (ch == 2) { // ctrl-B
                log_printf("--------------\r\n");
            } else if (ch == '\r') {
                if (menuState.mode == MENUMODE_NONE) {
                    start_menu_item_change();
                } else {
                    entry_buf[entry_buf_idx] = 0;
                    commit_menu_item_change();
                }
                sweep_service();
            } else if (menuState.mode == MENUMODE_ENTRY) {
                if (ch == 8) {
                    if (entry_buf_idx > 0) {
                        serial_write_buf[serial_write_buf_idx++] = 8;
                        serial_write_buf[serial_write_buf_idx++] = ' ';
                        serial_write_buf[serial_write_buf_idx++] = 8;
                        entry_buf_idx--;
                    }
                } else if (ch == 3) {    // ctrl-C
                    menuState.mode = MENUMODE_NONE;
                } else if (entry_buf_idx < sizeof(entry_buf)) {
                    entry_buf[entry_buf_idx++] = ch;
                    serial_write_buf[serial_write_buf_idx++] = ch;
                }
            } else if (menuState.mode == MENUMODE_NONE) {
                if (ishexchar(ch) || ch == '-') {   // characters which start entry
                    const value_item_t* vi = (const value_item_t*)menu_table[curpos.row][curpos.tableCol]->itemPtr;
                    if (vi->itemType == _ITEM_VALUE) {
                        start_value_entry(menu_table[curpos.row][curpos.tableCol]);
                        entry_buf[entry_buf_idx++] = ch;
                        serial_write_buf[serial_write_buf_idx++] = ch;
                    }
                } else if (ch == 'r') {
                    menuState.mode = MENUMODE_REDRAW;
                } else if (ch == '.') {
                    Radio::test();
                }

            }
            break;
        case URX_STATE_ESCAPE:
            uartRxTimeout.detach();
            if (ch == '[')
                uart_rx_state = URX_STATE_CSI;
            else {
#ifdef MENU_DEBUG
                log_printf("unhancled-esc:%02x\r\n", ch);
#endif /* MENU_DEBUG */
                uart_rx_state = URX_STATE_NONE;
            }
            break;
        case URX_STATE_CSI:
            if (menuState.mode == MENUMODE_NONE)
                navigate_menu(ch);
            else if (menuState.mode == MENUMODE_DROPDOWN)
                navigate_dropdown(ch);

            uart_rx_state = URX_STATE_NONE;
            //printf("\e[18;1f");  // set (force) cursor to row;column
            break;
    } // ..switch (uart_rx_state)

    if (serial_write_buf_idx > 0) {
        _pc->write(serial_write_buf, serial_write_buf_idx);
    }
}

void txDone()
{

    if (msg_type == MSG_TYPE_PER) {
        log_printf("CntPacketTx%u, max:%u  ipd%u\r\n", CntPacketTx, MaxNumPacket, tx_ipd_ms);
        if (++CntPacketTx <= MaxNumPacket) {
            std::chrono::microseconds us = std::chrono::microseconds(tx_ipd_ms * 1000);
            mbedTimeout.attach(next_tx_callback, us);
        }
    } else if (msg_type == MSG_TYPE_PINGPONG) {
        if (flags.ping_master) {
            ++CntPacketTx;
        }

        Radio::Rx();
    }
}

void
printRxPkt(uint8_t size)
{
    uint8_t col;
    char str[80];
    char *ptr, *endPtr;
    unsigned n = 0;
    endPtr = str + sizeof(str);
    ptr = str;
    col = 0;
    while (ptr < endPtr) {
        //sprintf(ptr, "%02x%c", Radio::radio.rx_buf[n], n == Radio::radio.rx_buf_offset ? '_' : ' '); // LR1110
        sprintf(ptr, "%02x ", Radio::radio.rx_buf[n]);
        ptr += 3;
        col += 3;
        if (col >= 77) {
            log_printf("%s\r\n", str);
            ptr = str;
            col = 0;
        }
        if (++n >= size)
            break;
    }
    log_printf("%s\r\n", str);
}

void rxDone(uint8_t size, float rssi, float snr)
{
    log_printf("rxDone %u, %.1fdBm  %.1fdB\r\n", size, rssi, snr);
    if (msg_type == MSG_TYPE_PACKET) {
        printRxPkt(size);
    } else if (msg_type == MSG_TYPE_PER) {
        if (memcmp(Radio::radio.rx_buf+4, PerMsg, 3) == 0) {
            unsigned i, PacketRxSequence = Radio::radio.rx_buf[0];
            PacketRxSequence <<= 8;
            PacketRxSequence += Radio::radio.rx_buf[1];
            PacketRxSequence <<= 8;
            PacketRxSequence += Radio::radio.rx_buf[2];
            PacketRxSequence <<= 8;
            PacketRxSequence += Radio::radio.rx_buf[3];

            CntPacketRxOK++;

            if (PacketRxSequence <= PacketRxSequencePrev || PacketRxSequencePrev == 0)
                i = 0;  // sequence reset to resync, dont count missed packets this time
            else
                i = PacketRxSequence - PacketRxSequencePrev - 1;


            CntPacketRxKO += i;
            RxTimeOutCount = 0;
            log_printf("PER rx%u  ok%u   ko%u\r\n", PacketRxSequence , CntPacketRxOK, CntPacketRxKO);

            PacketRxSequencePrev = PacketRxSequence;
        } // ..if PerMsg
        else {
            log_printf("per?\r\n");
            printRxPkt(size);
        }
    } else if (msg_type == MSG_TYPE_PINGPONG) {
        if (memcmp(Radio::radio.rx_buf+4, PingMsg, 4) == 0) {
            /* ping slave rx */
            Radio::setFS();
            receivedCntPacket = Radio::radio.rx_buf[0];
            receivedCntPacket <<= 8;
            receivedCntPacket += Radio::radio.rx_buf[1];
            receivedCntPacket <<= 8;
            receivedCntPacket += Radio::radio.rx_buf[2];
            receivedCntPacket <<= 8;
            receivedCntPacket += Radio::radio.rx_buf[3];
            log_printf("%u rxPing->txPong\r\n", receivedCntPacket);

            flags.ping_master = 0;
            flags.send_pong = 1;

        } else if (memcmp(Radio::radio.rx_buf+8, PongMsg, 4) == 0) {
            unsigned cnt;
            /* ping master rx */
            Radio::setFS();
            cnt = Radio::radio.rx_buf[0];
            cnt <<= 8;
            cnt += Radio::radio.rx_buf[1];
            cnt <<= 8;
            cnt += Radio::radio.rx_buf[2];
            cnt <<= 8;
            cnt += Radio::radio.rx_buf[3];
            log_printf("%u rxPong->txPing\r\n", cnt);
            flags.send_ping = 1;
        } else {
            log_printf("pingpong?\r\n");
            printRxPkt(size);
        }
    } else {
        /*for (unsigned n = 0; n < size; n++)
            log_printf("%02x\r\n", Radio::radio.rx_buf[n]);*/
        log_printf("msg_type %u\r\n", msg_type);
    }

}

const RadioEvents_t rev = {
    txDone,
    rxDone
};

volatile int foo;

int main()
{
    lfsr = LFSR_INIT;
    msg_type = MSG_TYPE_PACKET;

    uart_rx_state = URX_STATE_NONE;

    Radio::boardInit(&rev);

    {
        unsigned n;
        for (n = 0; n < MAX_MENU_ROWS; n++)
            StopMenuCols[n] = -1;
    }

    botRow = MAX_MENU_ROWS + SCROLLING_ROWS;

    printf("\e[7h"); // enable line wrapping
    printf("\e[%u;%ur", MAX_MENU_ROWS, botRow); // set scrolling region
    printf("\e[2J"); // erase entire screen

    full_menu_init();

    printf("\e[2J"); // erase entire screen

    menuState.mode = MENUMODE_NONE;

    draw_menu();

    curpos.row = 0;
    curpos.tableCol = 0;

    tx_ipd_ms = 100;

    for (;;) {
        if (_pc->readable()) {
            serial_callback();
        }

        if (flags.send_ping) {
            if (flags.pingpongEnable)
                SendPing();
            flags.send_ping = 0;
        }

        if (flags.send_pong) {
            if (flags.pingpongEnable)
                SendPong();
            flags.send_pong = 0;
        }

        if (flags.do_next_tx) {
            do_next_tx();
            flags.do_next_tx = 0;
        }

        if (menuState.mode == MENUMODE_REINIT_MENU) {
            full_menu_init();
            menuState.mode = MENUMODE_REDRAW;
        }

        if (menuState.mode == MENUMODE_REDRAW) {
            // erase entire screen, some dropdowns extend to scrolling area
            printf("\e[%u;%ur", MAX_MENU_ROWS, botRow); // set scrolling region, if terminal started after
            printf("\e[2J");
            //printf("\e[%u;1f\e[1J", MAX_MENU_ROWS);  // erase menu area

            menuState.mode = MENUMODE_NONE;
            draw_menu();
        }

        if (Radio::service(menuState.mode == MENUMODE_NONE ? LAST_CHIP_MENU_ROW : -1)) {
            read_menu_item(menu_table[curpos.row][curpos.tableCol], true);
        }

        if (cf_sweep.tick) {
            cf_sweep.tick = false;
#ifdef SX127x_H
            Radio::radio.set_frf_MHz(cf_sweep.MHz);
#else
            Radio::radio.setMHz(cf_sweep.MHz);
#endif
            cf_sweep.MHz += cf_sweep.step_MHz;
            if (cf_sweep.MHz >= cf_sweep.stop_MHz)
                cf_sweep.MHz = cf_sweep.start_MHz;
        }

    } // ..for (;;)
}

char strbuf[255];

void log_printf(const char* format, ...)
{
    va_list arglist;

    // put cursor at last scrolling-area line
    printf("\e[%u;1f", botRow);
    va_start(arglist, format);
    vsprintf(strbuf, format, arglist);
    va_end(arglist);

    printf("%s", strbuf);
}