transfer files from mbed-os block device (i.e. SD card) over LoRa radio.

Dependencies:   sx12xx_hal

radio chip selection

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

This program uses mbed serial terminal at 115200 8N1.
Files (on SD Card) are transferred between two LoRa devices.
Use ? question mark to see available commands on serial terminal.
Send file to other side using send /fs/somefile, then use cmp /fs/somefile to compare the file on the other side of radio link with local file with same name.
Use cmp to test radio link.

rm command to delete files must not include /fs/ directory prefix, filename only.
send and cmp require /fs/ directory.

sx1280 thruput

bytes/secsf12sf11sf10sf9sf8sf7sf6sf5
1600KHz2764821081188732775365640012000
800KHz5299701720280047666500
400KHz146522654033

sx126x thruput

bytes/secsf12sf11sf10sf9sf8sf7sf6sf5
500KHz2073817061257215435705608
250KHz103193344641111418873064
125KHz971733245479721607

sx127x thruput

sx126x recommended at 500KHz bandwidth.

bytes/secsf12sf11sf10sf9sf8sf7
500KHz11120738569712202050
250KHz48.71031923456321115
125KHz24.24497151313552
62.5KHz12.222.139.486157276

SD Card

SD card driver must be enabled in mbed_app.json with the line "target.components_add": ["SD"]
Pins for SD card connection can be overridden as shown in mbed_app.json.
The default pin configuration for SD card is found in mbed_lib.json in mbed-os/components/storage/blockdevice/COMPONENT_SD/.
If no pin configuration exists in this mbed_lib.json, or needs to be changed, the included mbed_app.json is example pin declaration for SD card.

FAT filesystem is only used in this project for demonstration purpose; for easily copying files to SD card.
However, for production projects, LittleFileSystem needs to be used instead for reliability and wear leveling.

Platforms such as K64F have SD card slot already.
See all boards with arduino shield connector and SD card here. (arduino connector needed for LoRa radio board)
...but if you want to use nucleo board: /media/uploads/dudmuck/nucleo_sdcardqtr.png

main.cpp

Committer:
Wayne Roberts
Date:
2019-06-05
Revision:
1:319ef808aaa4
Parent:
0:c3ecf7b252a3

File content as of revision 1:319ef808aaa4:

#include <stdio.h>
#include <errno.h>
#include "lorachip.h"

// how long extra to keep receiver open to wait for ack/nak, due to task performed by receiver rxdone handling
#define TX_RX_TURNAROUND_MS     70

#if defined(SX127x_H)
    #define BW_KHZ              500
    #define SPREADING_FACTOR    7
    #define CF_HZ               915000000
    #define TX_DBM              20
    #define SPI_HZ              5000000
#elif defined(SX126x_H)
    #define BW_KHZ              500
    #define SPREADING_FACTOR    5
    #define CF_HZ               915000000
    #define TX_DBM              (Radio::chipType == CHIP_TYPE_SX1262 ? 20 : 14) 
    #define SPI_HZ              10000000
#elif defined(SX128x_H)
    #define BW_KHZ              1600
    #define SPREADING_FACTOR    5
    #define CF_HZ               2487000000
    #define TX_DBM              2
    #define SPI_HZ              10000000
#endif
#define MAX_PKT_SIZE            250

EventQueue queue(32 * EVENTS_EVENT_SIZE);
Thread th;
Timer t;

typedef struct {
    uint8_t foobar           : 2; // 0,1
    uint8_t rxContStart      : 1; // 2
    uint8_t callRetry        : 1; // 3
    uint8_t txDonePending    : 1; // 4      txDone needs to be serviced
    uint8_t endOfFile        : 1; // 5
    uint8_t atTxDoneRxSingle : 1; // 6
    uint8_t verbose          : 1; // 7
} flags_t;
volatile flags_t flags;

//#include "LittleFileSystem.h"
//LittleFileSystem fs("fs");
#include "FATFileSystem.h"
FATFileSystem fs("fs");
/* FAT FS not recommended for production */
/* use Little FS for production */

//BlockDevice *bd = BlockDevice::get_default_instance();
#include "SDBlockDevice.h"
SDBlockDevice *bd = new SDBlockDevice(MBED_CONF_SD_SPI_MOSI, MBED_CONF_SD_SPI_MISO, MBED_CONF_SD_SPI_CLK, MBED_CONF_SD_SPI_CS, 5000000);

RawSerial pc(USBTX, USBRX);

//#define RX_DBG_PIN      PC_10
#ifdef RX_DBG_PIN
DigitalInOut rx_dbg(RX_DBG_PIN);
#endif /* RX_DBG_PIN */

volatile unsigned txMinDelay_us;     // minimum time between RX done to TX start
volatile unsigned add_txDelay_retry;     // how much extra to defer on each retry

#define MAX_ATTEMPTS        15
volatile uint8_t attemptsRemaining;
volatile int prevRxSeq;
volatile uint8_t seq;
volatile uint8_t tx_buf_len;
volatile unsigned xferStartAt;
volatile unsigned rxDoneAt;
volatile unsigned byteCnt;
int fd; // descriptor of transferring file
volatile int sendData_readLen;
lora_t current;

uint16_t myAddr;
#define BROADCAST_ADDRESS       0xffff
uint16_t peerAddr = BROADCAST_ADDRESS;

uint8_t file_buf[MAX_PKT_SIZE];

typedef enum {
    /* 0 */ STATE_NONE = 0,
    /* 1 */ STATE_RX_COMPARE,
    /* 2 */ STATE_SEND_FILE,
    /* 3 */ STATE_FILE_END,
    /* 4 */ STATE_GET_FILE
} state_e;
volatile state_e state;

char printBuf[2048];
volatile uint16_t printBuf_in;
volatile uint16_t printBuf_out;

/* Prints into circular buffer to prevent impact on execution speed.
 * (aka non-blocking printf)
 * Characters are sent in main loop, where time is less important. */
int vspfunc(const char *format, ...)
{
    va_list aptr;
    int ret;
    char buffer[256];

    va_start(aptr, format);
    ret = vsprintf(buffer, format, aptr);
    va_end(aptr);
    if (ret < 0) {
        pc.printf("vsprintf failed\r\n");
        return ret;
    }

    if ((printBuf_in + (unsigned)ret) >= sizeof(printBuf)) {
        int len_b, len_a = sizeof(printBuf) - printBuf_in;
        memcpy(printBuf + printBuf_in, buffer, len_a);
        len_b = ret - len_a;
        memcpy(printBuf, buffer + len_a, len_b);
        printBuf_in = len_b;
    } else {
        memcpy(printBuf + printBuf_in, buffer, ret);
        printBuf_in += ret;
    }

   return(ret);
}

volatile unsigned txDoneAt;

static inline void rxCont(void)
{
    Radio::Rx(0);
#ifdef RX_DBG_PIN
    rx_dbg.output();
    rx_dbg.write(0);
#endif /* RX_DBG_PIN */
}

void txDoneCB_botHalf()
{
    unsigned now;

    if (flags.atTxDoneRxSingle) {
        Radio::Rx(1000);
#ifdef RX_DBG_PIN
        rx_dbg.output();
        rx_dbg.write(0);
#endif /* RX_DBG_PIN */
        now = t.read_us();
        if (flags.verbose)
            vspfunc("rxSingle%u ", now - txDoneAt);
    } else {
        rxCont();
        Radio::Rx(0);
        now = t.read_us();
        if (flags.verbose)
            vspfunc("rxCont%u ", now - txDoneAt);
    }
    flags.txDonePending = false;
}

void txDoneCB_topHalf()
{
    txDoneAt = t.read_us();
    flags.txDonePending = true;
}

static uint16_t crc16( uint8_t *buffer, uint16_t length )
{
    uint16_t i;
    // The CRC calculation follows CCITT
    const uint16_t polynom = 0x1021;
    // CRC initial value
    uint16_t crc = 0x0000;
 
    if( buffer == NULL )
    {
        return 0;
    }
 
    for( i = 0; i < length; ++i )
    {
        uint16_t j;
        crc ^= ( uint16_t ) buffer[i] << 8;
        for( j = 0; j < 8; ++j )
        {
            crc = ( crc & 0x8000 ) ? ( crc << 1 ) ^ polynom : ( crc << 1 );
        }
    }
 
    return crc;
}


static inline unsigned rf_send(bool anytime, unsigned extratxdelay)
{
    unsigned sinceRxDone = 0;
    unsigned target = txMinDelay_us + extratxdelay;

    Radio::Standby();

    if (!anytime) {
        do {
            sinceRxDone = t.read_us() - rxDoneAt;
        } while (sinceRxDone < target);
    }

#ifdef RX_DBG_PIN
    rx_dbg.input();
    if (flags.verbose) {
        if (rx_dbg.read())
            vspfunc("\e[41mnotrxing\e[0m%u ", sinceRxDone);
        else
            vspfunc("\e[32mprxok\e[0m%u ", sinceRxDone);
    }
#endif /* RX_DBG_PIN */

    Radio::Send(tx_buf_len, 0, 0, 0);
    return sinceRxDone;
}

static void sendAck(bool ack, unsigned addTxDelay)
{
    uint16_t crc;

    tx_buf_len = 0;
    Radio::radio.tx_buf[tx_buf_len++] = Radio::radio.rx_buf[0];
    Radio::radio.tx_buf[tx_buf_len++] = ack ? 'Y' : 'N';
    crc = crc16(Radio::radio.tx_buf, tx_buf_len);
    Radio::radio.tx_buf[tx_buf_len++] = crc;
    Radio::radio.tx_buf[tx_buf_len++] = crc >> 8;

    flags.atTxDoneRxSingle = false;
    attemptsRemaining = MAX_ATTEMPTS;
    /*sinceRxDone = */rf_send(false, addTxDelay);
    if (flags.verbose)
        vspfunc("sentAck'%c' %u ", Radio::radio.tx_buf[1], addTxDelay);
}

void retry(bool anytime)
{
    if (attemptsRemaining == 0)
        return;

    if (--attemptsRemaining > 0) {
        flags.atTxDoneRxSingle = true;
        rf_send(anytime, 0);

        vspfunc("retry%u '%c'\r\n", attemptsRemaining, Radio::radio.tx_buf[1]);
    } else {
        vspfunc("retry end\r\n");
        state = STATE_NONE;
        rxCont();
    }
}

void sendError(const char* str)
{
    uint16_t crc;
    char* dest;

    tx_buf_len = 0;
    Radio::radio.tx_buf[tx_buf_len++] = Radio::radio.rx_buf[0];
    Radio::radio.tx_buf[tx_buf_len++] = 'E';
    dest = (char*)(&Radio::radio.tx_buf[tx_buf_len]);
    strcpy(dest, str);
    tx_buf_len += strlen(str);

    crc = crc16(Radio::radio.tx_buf, tx_buf_len);
    Radio::radio.tx_buf[tx_buf_len++] = crc;
    Radio::radio.tx_buf[tx_buf_len++] = crc >> 8;

    flags.atTxDoneRxSingle = false;

    rf_send(false, 0);
}

void readData()
{
    sendData_readLen = read(fd, file_buf, sizeof(file_buf));
    if (sendData_readLen < (int)sizeof(file_buf)) {
        /* this is last data */
        flags.endOfFile = true;
    }
}

void sendData()
{
    uint16_t crc;
    tx_buf_len = 0;
    Radio::radio.tx_buf[tx_buf_len++] = ++seq;
    Radio::radio.tx_buf[tx_buf_len++] = 'D';

    if (sendData_readLen > 0) {
        memcpy(Radio::radio.tx_buf + tx_buf_len, file_buf, sendData_readLen);
        tx_buf_len += sendData_readLen;
        byteCnt += sendData_readLen;
    }

    crc = crc16(Radio::radio.tx_buf, tx_buf_len);
    Radio::radio.tx_buf[tx_buf_len++] = crc;
    Radio::radio.tx_buf[tx_buf_len++] = crc >> 8;

    flags.atTxDoneRxSingle = true;
    attemptsRemaining = MAX_ATTEMPTS;
    /*sinceRxDone = */rf_send(false, 0);

    if (flags.verbose)
        vspfunc("sendTotal:%u ", byteCnt);
}

volatile uint8_t rxSize;

void rxHandler(uint8_t* rxBuf)
{
    static const char* pendingError = NULL;
    uint16_t calc_crc, rx_crc;
    static uint8_t sameCnt;

    calc_crc = crc16(rxBuf, rxSize-2);
    rx_crc = rxBuf[rxSize-1];
    rx_crc <<= 8;
    rx_crc += rxBuf[rxSize-2];
    if (rx_crc != calc_crc) {
        /* when received crc fails, only send NAK when this received message isnt a NAK or ACK */
        if (rxBuf[1] == 'Z' || rxBuf[1] == 'D' || rxBuf[1] == 'F') {
            sendAck(false, 0);
            vspfunc("naking '%c' %u\r\n", rxBuf[1], rxBuf[0]);
        }
        vspfunc("\e[41mcalc_crc:%04x, rx_crc:%04x\e[0m\r\n", calc_crc, rx_crc);
        rf_send(false, 0);
        return;
    }

    if (flags.verbose) {
        vspfunc(" rxseq%d->%u '%c' ", prevRxSeq, rxBuf[0], rxBuf[1]);
    }

    if (rxBuf[1] == 'E') {
        rxBuf[rxSize-2] = 0; // null terminate error msg
        vspfunc("from remote \"%s\" ", rxBuf + 2);
    }

    switch (state) {
        case STATE_NONE:
            if (flags.verbose)
                vspfunc("STATE_NONE ");

            if (rxBuf[1] == 'R') {
                uint16_t rxAddr;
                rxBuf[rxSize-2] = 0; // null terminate file name
                rxAddr = rxBuf[3];
                rxAddr <<= 8;
                rxAddr += rxBuf[2];
                vspfunc(" addr %04x ", rxAddr);
                if (myAddr == rxAddr || BROADCAST_ADDRESS == rxAddr) {
                    if (flags.verbose)
                        vspfunc(" open-read %s, ", rxBuf + 4);
                    fd = open((char*)(rxBuf + 4), O_RDONLY);
                    if (flags.verbose)
                        vspfunc("fd:%d ", fd);
                    if (fd < 0) {
                        vspfunc("open %s failed\n", (char*)(rxBuf + 2));
                        sendError(strerror(errno));
                        break;
                    }
                    state = STATE_SEND_FILE;
                    xferStartAt = t.read_us();
                    byteCnt = 0;
                    seq = 0;
                    flags.endOfFile = false;
                    attemptsRemaining = MAX_ATTEMPTS;
                    readData();
                    sendData();
                } else
                    vspfunc("ignore 'R'\r\n");
            } else if (rxBuf[1] == 'F') {
                uint16_t rxAddr;
                rxBuf[rxSize-2] = 0; // null terminate file name
                rxAddr = rxBuf[3];
                rxAddr <<= 8;
                rxAddr += rxBuf[2];
                vspfunc(" addr %04x ", rxAddr);
                if (myAddr == rxAddr || BROADCAST_ADDRESS == rxAddr) {
                    vspfunc("open-write %s ", rxBuf + 4);
                    fd = open((char*)(rxBuf + 4), O_WRONLY | O_CREAT | O_TRUNC);
                    vspfunc("fd:%d ", fd);
                    if (fd < 0) {
                        sendError(strerror(errno));
                    } else {
                        sendAck(true, 0);
                        xferStartAt = t.read_us();
                        byteCnt = 0;
                        state = STATE_GET_FILE;
                        sameCnt = 0;
                    }
                } else
                    vspfunc("ignore 'F'\r\n", rxAddr);
            } else if (rxBuf[1] == 'Z') {
                /* probably previous ack wasnt received of just completed transfer */
                sendAck(true, 0);
            }
            break;
        case STATE_GET_FILE:
            vspfunc("STATE_GET_FILE fd%d ", fd);
            if (rxBuf[1] == 'D') {
                if (prevRxSeq+1 == rxBuf[0]) {
                    uint8_t len = rxSize - 4;
                    int fop_len = write(fd, rxBuf + 2, len);
                    if (fop_len < 0) {
                        sendError(strerror(errno));
                        vspfunc(strerror(errno));
                    } else if (fop_len < len) {
                        sendError("short-write");
                        vspfunc("write %d vs %d", fop_len, len);
                    } else {
                        byteCnt += fop_len;
                        sendAck(true, 0);
                        vspfunc("ok total %u ", byteCnt);
                    }
                } else if (prevRxSeq == rxBuf[0]) {
                    vspfunc("\e[43mrepeat %u\e[0m", rxBuf[0]);
                    /* ignore do nothing, ack to get next */
                    sendAck(true, 0);
                } else {
                    vspfunc("\e[41msequence %u %u\e[0m", prevRxSeq, rxBuf[0]);
                    sendError("sequence");
                    state = STATE_NONE;
                    close(fd);
                }
            } else if (rxBuf[1] == 'Z') {
                unsigned duration_us;
                float secs;
                sendAck(true, 0);
                duration_us = t.read_us() - xferStartAt;
                secs = duration_us / 1000000.0;
                close(fd);
                vspfunc("EOF total:%u = %.1f bytes/sec\r\n", byteCnt , byteCnt / secs);
                state = STATE_NONE;
            } else if (rxBuf[1] == 'F' && rxBuf[0] == 0) { /* check filename? */
                if (prevRxSeq == rxBuf[0])
                    sameCnt++;
                else
                    sameCnt = 0;

                vspfunc("didnt-get-F-ack fd:%d ", fd);
                sendAck(true, sameCnt * add_txDelay_retry);
            }
            break;
        case STATE_SEND_FILE:
            if (flags.verbose)
                vspfunc("STATE_SEND_FILE  ");

            if (rxBuf[1] == 'Y') {
                if (flags.endOfFile) {
                    uint16_t crc;
                    tx_buf_len = 0;
                    Radio::radio.tx_buf[tx_buf_len++] = ++seq;
                    Radio::radio.tx_buf[tx_buf_len++] = 'Z';
                    crc = crc16(Radio::radio.tx_buf, tx_buf_len);
                    Radio::radio.tx_buf[tx_buf_len++] = crc;
                    Radio::radio.tx_buf[tx_buf_len++] = crc >> 8;

                    flags.atTxDoneRxSingle = true;
                    attemptsRemaining = MAX_ATTEMPTS;
                    /*sinceRxDone = */rf_send(false, 0);
                    close(fd);
                    state = STATE_FILE_END;
                } else {
                    readData();
                    sendData();
                }
            } else if (rxBuf[1] == 'R') {
                /* peer didnt get ack for 'R' */
                retry(false);
            } else if (rxBuf[1] == 'N') {
                /* NAK, peer crc fail */
                retry(false);
            } else if (rxBuf[1] == 'E') {
                state = STATE_NONE;
                close(fd);
                rxCont();
                vspfunc("xfer-ended\r\n");
            }
            break;
        case STATE_FILE_END:
            vspfunc("STATE_FILE_END ");
            if (rxBuf[1] == 'Y') {
                unsigned duration_us = t.read_us() - xferStartAt;
                float secs = duration_us / 1000000.0;
                vspfunc("total:%u = %.1f bytes/sec\r\n", byteCnt, byteCnt / secs);
                state = STATE_NONE;
                rxCont();
            } else
                vspfunc("badack:%c ", rxBuf[1]);
            break;
        case STATE_RX_COMPARE:
            if (flags.verbose)
                vspfunc("STATE_RX_COMPARE ");

            if (pendingError) {
                sendError(pendingError);
                vspfunc("sentErr:%s ", pendingError);
                pendingError = NULL;
                state = STATE_NONE;
                break;
            }

            if (rxBuf[1] == 'D') {
                if (prevRxSeq == -1 || rxBuf[0] > prevRxSeq) {
                    int ret, rxLen = rxSize - 4;
                    sameCnt = 0;
                    ret = read(fd, file_buf, rxLen);
                    if (ret < 0) {
                        pendingError = "read fail";
                        vspfunc("readFail");
                    } else if (ret < rxLen) {
                        pendingError = "read short";
                        vspfunc("readShort");
                    } else if (memcmp(file_buf, rxBuf + 2, ret) == 0) {
                        sendAck(true, 0);
                        byteCnt += ret;
                        if (flags.verbose)
                            vspfunc("cmpTotal:%u ", byteCnt);
                    } else {
                        int n;
                        pendingError = "memcmp fail";
                        vspfunc("memcmp");
                        vspfunc("\r\nrxBuf:\r\n");
                        for (n = 0; n < ret; n++) {
                            if (rxBuf[n+2] != file_buf[n])
                                vspfunc("\e[41m");
                            vspfunc("%02x ", rxBuf[n+2]);
                            if (rxBuf[n+2] != file_buf[n])
                                vspfunc("\e[0m");
                            if ((n & 0xf) == 0xf)
                                vspfunc("\r\n");
                        }
                        vspfunc("\r\nfile_buf:\r\n");
                        for (n = 0; n < ret; n++) {
                            if (rxBuf[n+2] != file_buf[n])
                                vspfunc("\e[41m");
                            vspfunc("%02x ", file_buf[n]);
                            if (rxBuf[n+2] != file_buf[n])
                                vspfunc("\e[0m");
                            if ((n & 0xf) == 0xf)
                                vspfunc("\r\n");
                        }
                        vspfunc("\r\n");
                    }
                } else {
                    if (rxBuf[0] == prevRxSeq)
                        sameCnt++;
                    else
                        sameCnt = 0;

                    sendAck(true, sameCnt * add_txDelay_retry);
                    vspfunc("repeat ");
                }
            } else if (rxBuf[1] == 'Z') {
                unsigned duration_us = t.read_us() - xferStartAt;
                float secs = duration_us / 1000000.0;
                vspfunc("EOF total:%u = %.1f bytes/sec\r\n", byteCnt, byteCnt / secs);
                close(fd);
                sendAck(true, 0);
                state = STATE_NONE;
            } else if (rxBuf[1] == 'E') {
                sendAck(true, 0);
                state = STATE_NONE;
            }
            break;
    } // ..switch (state)

    prevRxSeq = rxBuf[0];
    if (flags.verbose)
        vspfunc("\r\n");
}

void rxDoneCB(uint8_t size, float rssi, float snr)
{
    rxDoneAt = t.read_us();

    if (rxSize != 0)
        vspfunc("\e[41mrxoverrun\e[0m ");
    else {
        rxSize = size;
        if (flags.verbose)
            vspfunc("rxDone ");
    }
    /* cant TX here because irq flags cleared after returning */
}

void rxTimeoutCB()
{
    vspfunc("\e[33mrxTimeout ");

    if (state != STATE_NONE)
        flags.callRetry = true;
    else {
        vspfunc("spurious");
        flags.rxContStart = true;
    }

    vspfunc("\e[0m ");
}

const RadioEvents_t rev = {
    /* Dio0_top_half */     NULL,
    /* TxDone_topHalf */    txDoneCB_topHalf,    // called in ISR context
    /* TxDone_botHalf */    txDoneCB_botHalf,   // called from service()
    /* TxTimeout  */        NULL,
    /* RxDone  */           rxDoneCB,       // called from service()
    /* RxTimeout  */        rxTimeoutCB,    // called from service()
    /* RxError  */          NULL,
    /* FhssChangeChannel  */NULL,
    /* CadDone  */          NULL
};

char pcbuf[64];
volatile int pcbuf_len;
volatile uint8_t pcbuf_idx;

typedef struct {
    const char* const cmd;
    void (*handler)(uint8_t args_at);
    const char* const arg_descr;
    const char* const description;
} menu_item_t;

void cmd_help(uint8_t);

void cmd_print_status(uint8_t idx)
{
    pc.printf("state%u ", state);

    if (flags.atTxDoneRxSingle)
        pc.printf("flags.atTxDoneRxSingle ");
    if (flags.endOfFile)
        pc.printf("flags.endOfFile ");

    print_lora_status();
}

void cmd_stby(uint8_t argsAt)
{
    Radio::Standby();
#ifdef RX_DBG_PIN
    rx_dbg.input();
#endif /* RX_DBG_PIN */
    pc.printf("standby\r\n");
}

void cmd_verbose(uint8_t args_at)
{
    flags.verbose ^= 1;
    pc.printf("verbose:%u\r\n", flags.verbose);
}

void cmd_pt(uint8_t args_at)
{
    char foo[32];
    unsigned cnt, n;
    if (sscanf(pcbuf + args_at, "%u", &cnt) == 1) {
        for (n = 0; n < cnt; n++) {
            foo[n] = 'a' + n;
        }
        for (n = 0; n < cnt; n++) {
            vspfunc("in%u: test %s\r\n", printBuf_in, foo);
            //vspfunc("in%u: test\r\n", printBuf_in);
        }
    } else
        vspfunc("give count number\r\n");
}

void cmd_peerAddr(uint8_t args_at)
{
    unsigned a;
    if (sscanf(pcbuf + args_at, "%x", &a) == 1) {
        peerAddr = a;
    }
    pc.printf("peerAddr:%04x\r\n", peerAddr);
}

void cmd_myAddr(uint8_t args_at)
{
    unsigned a;
    if (sscanf(pcbuf + args_at, "%x", &a) == 1) {
        myAddr = a;
    }
    pc.printf("myAddr:%04x\r\n", myAddr);
}

void cmd_ls(uint8_t args_at)
{
    int err;

    // Display the root directory
    pc.printf("Opening the root directory... ");
    fflush(stdout);
    DIR *d = opendir("/fs/");
    pc.printf("%s\r\n", (!d ? "Fail :(" : "OK"));
    if (!d) {
        error("error: %s (%d)\r\n", strerror(errno), -errno);
    }

    pc.printf("root directory:\r\n");
    while (true) {
        struct stat st;
        struct dirent *e = readdir(d);
        if (!e) {
            break;
        }
        fs.stat(e->d_name, &st);

        pc.printf("    %s\t%lu\r\n", e->d_name, st.st_size);
    }

    //pc.printf("Closing the root directory... ");
    fflush(stdout);
    err = closedir(d);
    pc.printf("%s\r\n", (err < 0 ? "Fail :(" : "OK"));
    if (err < 0) {
        error("error: %s (%d)\r\n", strerror(errno), -errno);
    }
}

void cmd_md(uint8_t args_at)
{
    unsigned us;

    if (sscanf(pcbuf + args_at, "%u", &us) == 1) {
        txMinDelay_us = us;
    }
    pc.printf("txMinDelay_us:%u\r\n", txMinDelay_us);
}

void cmd_cat(uint8_t argsAt)
{
    unsigned ofs;
    uint8_t buf[16];
    int ret, fd = open(pcbuf + argsAt, O_RDONLY);
    if (fd < 0) {
        pc.printf("open %s failed\r\n", pcbuf + argsAt);
        return;
    }

    ofs = 0;
    do {
        int n;
        ret = read(fd, buf, sizeof(buf));
        if (ret < 0) {
            pc.printf("read failed\n");
            return;
        } 
        pc.printf("%04x: ", ofs);
        for (n = 0; n < ret; n++)
            pc.printf("%02x ", buf[n]);
        pc.printf("   ");
        for (n = 0; n < ret; n++) {
            if (buf[n] >= ' ')
                pc.putc(buf[n]);
            else
                pc.putc('.');
        }
        pc.putc('\r');
        pc.putc('\n');

        ofs += ret;
        if (pcbuf_len < 0) {
            pcbuf_len = 0;
            break;
        }
        if ((ofs & 0xff) == 0) {
            uint8_t x = pcbuf_idx;
            pc.printf("hitkey:");
            while (x == pcbuf_idx && pcbuf_len >= 0)
                ;
            pc.printf("\r\n");
        }
    } while (ret > 0);

    close(fd);
}

void cmd_rm(uint8_t args_at)
{
    int ret = fs.remove(pcbuf + args_at);
    if (ret < 0)
        perror("unlink");
    else
        pc.printf("delete %s ok\r\n", pcbuf + args_at);
}

void cmd_rx(uint8_t argsAt)
{
    rxCont();
    pc.printf("rx start\r\n");
}

void cmd_send(uint8_t argsAt)
{
    char* dest;
    uint8_t txlen;
    uint16_t crc;

    if (fd > 0)
        close(fd);

    fd = open(pcbuf + argsAt, O_RDONLY);
    if (fd < 0) {
        pc.printf("open %s failed\r\n", pcbuf + argsAt);
        return;
    }
    txlen = strlen(pcbuf + argsAt);

    seq = 0;
    prevRxSeq = -1;
    Radio::radio.tx_buf[0] = seq;
    Radio::radio.tx_buf[1] = 'F';
    Radio::radio.tx_buf[2] = peerAddr;
    Radio::radio.tx_buf[3] = peerAddr >> 8;

    dest = (char*)(&Radio::radio.tx_buf[4]);
    strcpy(dest, pcbuf + argsAt);
    tx_buf_len = txlen + 4;

    crc = crc16(Radio::radio.tx_buf, tx_buf_len);
    Radio::radio.tx_buf[tx_buf_len++] = crc;
    Radio::radio.tx_buf[tx_buf_len++] = crc >> 8;

    byteCnt = 0;
    attemptsRemaining = MAX_ATTEMPTS;
    flags.endOfFile = false;
    state = STATE_SEND_FILE;

    flags.atTxDoneRxSingle = true;  // receiver acks (F)
    xferStartAt = t.read_us();
    rf_send(true, 0);
}

void cmd_cmp(uint8_t argsAt)
{
    char* dest;
    uint8_t txlen;
    uint16_t crc;

    Radio::Standby();

    if (fd > 0)
        close(fd);

    fd = open(pcbuf + argsAt, O_RDONLY);
    if (fd < 0) {
        pc.printf("open %s failed\n", pcbuf + argsAt);
        return;
    }
    txlen = strlen(pcbuf + argsAt);

    seq = 0;
    prevRxSeq = -1;
    Radio::radio.tx_buf[0] = seq;
    Radio::radio.tx_buf[1] = 'R';
    Radio::radio.tx_buf[2] = peerAddr;
    Radio::radio.tx_buf[3] = peerAddr >> 8;

    dest = (char*)(&Radio::radio.tx_buf[4]);
    strcpy(dest, pcbuf + argsAt);
    tx_buf_len = txlen + 4;

    crc = crc16(Radio::radio.tx_buf, tx_buf_len);
    Radio::radio.tx_buf[tx_buf_len++] = crc;
    Radio::radio.tx_buf[tx_buf_len++] = crc >> 8;

    byteCnt = 0;
    flags.endOfFile = false;
    attemptsRemaining = MAX_ATTEMPTS;

    flags.atTxDoneRxSingle = false;
    xferStartAt = t.read_us();
    rf_send(true, 0);
    state = STATE_RX_COMPARE;
    pc.printf("->STATE_RX_COMPARE\r\n");
}

void cmd_um(uint8_t args_at)
{
    int err;

    // Tidy up
    pc.printf("Unmounting... ");
    fflush(stdout);
    err = fs.unmount();
    pc.printf("%s\n", (err < 0 ? "Fail :(" : "OK"));
    if (err < 0) {
        error("error: %s (%d)\n", strerror(-err), err);
    }
}

void cmd_op(uint8_t argsAt)
{
    int i;

    if (sscanf(pcbuf + argsAt, "%d", &i) == 1)
        Radio::set_tx_dbm(i);

    tx_dbm_print();
    pc.printf("\r\n");
}

const menu_item_t menu_items[] = 
{   /* after first character, command names must be [A-Za-z] */
    { "stby", cmd_stby, "", "radio standby" },
    { "sf", cmd_sf, "<sf>", "set sf" },
    { "bw", cmd_bw, "<KHz>", "set bw" },
    { "rx", cmd_rx, "", "radio rx" },
    { "v", cmd_verbose, "", "toggle verbose printing" },
    { "frf", cmd_frf, "<MHz>", "radio center frequency" },
    { "op", cmd_op, "<dBm>", "tx dbm" },
    { "ls", cmd_ls, "", "directory list" },
    { "paddr", cmd_peerAddr, "<hex>", "get/set peer address" },
    { "maddr", cmd_myAddr, "<hex>", "get/set my address" },
    { "pt", cmd_pt, "", "print test" },
    { "um", cmd_um, "", "unmount" },
    { "md", cmd_md, "<microseconds>", "set minimum delay between rxDone and tx start" },
    { "cat", cmd_cat, "<filename>", "print file content" },
    { "send", cmd_send, "<filename>", "send file over radio" },
    { "rm", cmd_rm, "<filename>", "delete local file (without directory)" },
    { "cmp", cmd_cmp, "<filename>", "compare remote file against local" },
    { ".", cmd_print_status, "","print status"},
    { "?", cmd_help, "","this list of commands"},
    { NULL, NULL, NULL, NULL }
};

void cmd_help(uint8_t args_at)
{
    int i;
    
    for (i = 0; menu_items[i].cmd != NULL ; i++) {
        pc.printf("%s%s\t%s\r\n", menu_items[i].cmd, menu_items[i].arg_descr, menu_items[i].description);
    }
}

void
console()
{
    int i;
    uint8_t user_cmd_len;
    
    if (pcbuf_len < 0) {
        pc.printf("abort\r\n");
        Radio::Standby();
        wait(0.02);
        rxCont();
        pcbuf_len = 0;
        state = STATE_NONE;
        return;
    }
    if (pcbuf_len == 0) {
        return;
    }
        
    pc.printf("\r\n");
        
    /* get end of user-entered command */
    user_cmd_len = 1;   // first character can be any character
    for (i = 1; i <= pcbuf_len; i++) {
        if (pcbuf[i] < 'A' || (pcbuf[i] > 'Z' && pcbuf[i] < 'a') || pcbuf[i] > 'z') {
            user_cmd_len = i;
            break;
        }
    }
 
    for (i = 0; menu_items[i].cmd != NULL ; i++) {
        int mi_len = strlen(menu_items[i].cmd);
 
        if (menu_items[i].handler && user_cmd_len == mi_len && (strncmp(pcbuf, menu_items[i].cmd, mi_len) == 0)) {
            while (pcbuf[mi_len] == ' ')   // skip past spaces
                mi_len++;
            menu_items[i].handler(mi_len);
            break;
        }
    }
   
    pcbuf_len = 0;
    pc.printf("> ");
    fflush(stdout); 
}

void rx_callback()
{
    static uint8_t prev_len = 0;;
    char c = pc.getc();

    if (c == 8) {
        if (pcbuf_idx > 0) {
            pc.putc(8);
            pc.putc(' ');
            pc.putc(8);
            pcbuf_idx--;
        }
    } else if (c == 3) {    // ctrl-C
        pcbuf_len = -1;
        queue.call(console);
    } else if (c == '\r') {
        if (pcbuf_idx == 0) {
            pcbuf_len = prev_len;
        } else {
            pcbuf[pcbuf_idx] = 0;   // null terminate
            prev_len = pcbuf_idx;
            pcbuf_idx = 0;
            pcbuf_len = prev_len;
        }
        queue.call(console);
    } else if (pcbuf_idx < sizeof(pcbuf)) {
        pcbuf[pcbuf_idx++] = c;
        pc.putc(c);
    }
}

void set_symb_timeout()
{
    float symbPeriod_ms = (1 << current.sf) / (float)current.bwKHz;
    float rxtx_turnaround_symbols = TX_RX_TURNAROUND_MS / symbPeriod_ms;
    pc.printf("sp:%.3f, toSymbs:%.1f\r\n", symbPeriod_ms, rxtx_turnaround_symbols);
#if defined(SX126x_H)
    if (rxtx_turnaround_symbols > (254-8)) {
        rxtx_turnaround_symbols = 254-8;
        pc.printf("\e[41msymbTo hit top\e[0m\r\n");
    }
#endif
    rxtx_turnaround_symbols = ceil(rxtx_turnaround_symbols);
    pc.printf("turnSymb:%f\r\n", rxtx_turnaround_symbols);
    Radio::SetLoRaSymbolTimeout(rxtx_turnaround_symbols + 8);
}

int main()
{
#ifdef RX_DBG_PIN
    rx_dbg.input();
    rx_dbg.mode(PullUp);
#endif /* RX_DBG_PIN */

    pc.attach(rx_callback);
    pc.baud(115200);
    pc.printf("--- Mbed OS filesystem example ---\n");

    Radio::Init(&rev, SPI_HZ);
    radio_readChip();
 
    Radio::Standby();
    wait(0.05);
    Radio::LoRaModemConfig(BW_KHZ, SPREADING_FACTOR, 1);
    Radio::LoRaPacketConfig(8, false, true, false);  // preambleLen, fixLen, crcOn, invIQ
    Radio::SetChannel(CF_HZ);
 
    Radio::set_tx_dbm(TX_DBM);

    current.bwKHz = BW_KHZ;
    current.sf = SPREADING_FACTOR;
    set_symb_timeout();
    t.start();

    wait(0.05);
    rxCont();
    flags.verbose = true;

    // Try to mount the filesystem
    pc.printf("Mounting the filesystem... ");
    fflush(stdout);
    int err = fs.mount(bd);
    pc.printf("%s\r\n", (err ? "Fail :(" : "OK\r\n"));
    if (err) {
        // Reformat if we can't mount the filesystem
        // this should only happen on the first boot
        pc.printf("No filesystem found, formatting... ");
        fflush(stdout);
        err = fs.reformat(bd);
        pc.printf("%s\r\n", (err ? "Fail :(" : "OK\r\n"));
        if (err) {
            error("error: %s (%d)\r\n", strerror(-err), err);
        }
    }

    txMinDelay_us = 2000;
    add_txDelay_retry = 400;
    th.start(callback(&queue, &EventQueue::dispatch_forever));

    for (;;) {
        Radio::service();

        if (rxSize != 0) {
            rxHandler(Radio::radio.rx_buf);
            rxSize = 0;
        }

        if (flags.rxContStart) {
            rxCont();
            flags.rxContStart = false;
        }

        if (flags.callRetry) {
            retry(true);
            flags.callRetry = false;
        }

        /* servicing txDone is higher priority than printing characters */
        while (!flags.txDonePending && printBuf_in != printBuf_out) {
            pc.putc(printBuf[printBuf_out]);
            if (++printBuf_out == sizeof(printBuf))
                printBuf_out = 0;
        }
    } // ..for (;;)
}