firm newest

Dependencies:   MTS-Serial libmDot-dev-mbed5-deprecated

CommandTerminal/CommandTerminal.cpp

Committer:
nguyenhoang9x5555
Date:
2018-10-22
Revision:
1:c1c6549b9333
Parent:
0:3c869a8cb8f8

File content as of revision 1:c1c6549b9333:

#include "ctype.h"
#include "CommandTerminal.h"
#include "Command.h"
#include "MTSLog.h"
#include "ChannelPlan.h"
#include <cstdarg>
#include <deque>
#if defined(TARGET_XDOT_L151CC)
#include "xdot_low_power.h"
#endif

#if defined(TARGET_MTS_MDOT_F411RE)
const char CommandTerminal::banner[] = "\r\n\nMultiTech Systems LoRa XBee Module\r\n\n";
#else
const char CommandTerminal::banner[] = "\r\n\nMultiTech Systems LoRa xDot Module\r\n\n";
#endif
const char CommandTerminal::helpline[] = "Enter '?' for help\r\n";

const char CommandTerminal::newline[] = "\r\n";

// Command error text...
const char CommandTerminal::command_error[] = "Command not found!\r\n";

// Response texts...
const char CommandTerminal::help[] = "\r\nHelp\r\n";
const char CommandTerminal::cmd_error[] = "Invalid command\r\n";
const char CommandTerminal::connect[] = "\r\nCONNECT\r\n";
const char CommandTerminal::no_carrier[] = "\r\nNO CARRIER\r\n";
const char CommandTerminal::done[] = "\r\nOK\r\n";
const char CommandTerminal::error[] = "\r\nERROR\r\n";

// Escape sequence...
const char CommandTerminal::escape_sequence[] = "+++";

mts::ATSerial* CommandTerminal::_serialp = NULL;
mDot* CommandTerminal::_dot = NULL;

CommandTerminal::RadioEvent* CommandTerminal::_events = new RadioEvent();

static bool serial_data_mode = false;
static bool peer_to_peer = false;

std::string CommandTerminal::_errorMessage = "";

void CommandTerminal::setErrorMessage(const char* message) {
    _errorMessage.assign(message);
}

void CommandTerminal::setErrorMessage(const std::string& message) {
    _errorMessage.assign(message);
}

const Command CommandTerminal::_commands[NO_OF_COMMANDS] = {
    CmdAttention(),
    CmdIdentification(),
    CmdResetCpu(),
    CmdDummy("Enable/Disable Echo", "ATE", "ATE0: disable, ATE1: enable", "(0,1)"),
    CmdDummy("Enable/Disable Verbose", "ATV", "ATV0: disable, ATV1: enable", "(0,1)"),
    CmdDummy("Hardware Flow Control", "AT&K", "AT&K0: disable, AT&K3: enable", "(0,3)"),

    CmdFactoryDefault(),
    CmdSaveConfig(),
    CmdDisplayConfig(),
    CmdDisplayStats(),
    CmdResetStats(),
    CmdSerialBaudRate(),
    CmdDebugBaudRate(),
    CmdStartUpMode(),

    CmdDefaultFrequencyBand(),
    CmdFrequencyBand(),
    CmdFrequencySubBand(),
    CmdPublicNetwork(),
    CmdDeviceId(),
    CmdDeviceClass(),
    CmdLbt(),

    CmdAppPort(),
    CmdNetworkAddress(),
    CmdNetworkSessionKey(),
    CmdDataSessionKey(),
    CmdUplinkCounter(),
    CmdDownlinkCounter(),
    CmdSaveSession(),
    CmdRestoreSession(),
    CmdNetworkKey(),
    CmdNetworkId(),

    CmdJoinDelay(),
// Remove join settings commands until valid case for changing default settings
//    CmdJoinRx1Offset(),
//    CmdJoinRx2Datarate(),
//    CmdJoinRx2Frequency(),
    CmdJoinRequest(),
    CmdJoinRetries(),
    CmdJoinByteOrder(),
    CmdNetworkJoinMode(),
    CmdPreserveSession(),
    CmdNetworkJoinStatus(),
    CmdNetworkLinkCheck(),
    CmdLinkCheckCount(),
    CmdLinkCheckThreshold(),
    CmdEncryption(),

    CmdRssi(),
    CmdSnr(),
    CmdDataPending(),

    CmdSessionDataRate(),
    CmdChannelMask(),

    CmdTxDataRate(),
    CmdTxPower(),
    CmdAntennaGain(),
    CmdTxFrequency(),
    CmdTxInverted(),
    CmdTxWait(),
    CmdTxChannel(),
    CmdTxNextMs(),
    CmdTimeOnAir(),

    CmdRxDelay(),
    CmdRxOutput(),
    CmdRxInverted(),

    CmdErrorCorrection(),
    CmdCRC(),
    CmdAdaptiveDataRate(),

    CmdACKAttempts(),
    CmdRepeat(),
    CmdMacCmd(),

    CmdSendString(),
    CmdSendBinary(),
    CmdReceiveOnce(),

    CmdDummy("Serial Data Mode", "AT+SD", "Enter serial data mode, exit with '+++'", "NONE"),
    CmdDummy("Sleep Mode", "AT+SLEEP", "Enter sleep mode (0:deepsleep,1:sleep)", "(0,1)"),
    CmdSerialClearOnError(),
    CmdWakeMode(),
    CmdWakeInterval(),
    CmdWakePin(),
    CmdWakeDelay(),
    CmdWakeTimeout(),
    CmdPing(),
    CmdLogLevel(),

    CmdDummy("***** Test Commands *****", "", "", ""),
    CmdRxDataRate(),
    CmdRxFrequency(),
    CmdReceiveContinuous(),
    CmdSendStringOnInterval(),

#ifdef MTS_RADIO_DEBUG_COMMANDS
    CmdDummy("***** Debug Commands *****", "", "", ""),
    CmdSendContinuous(),
    CmdWriteProtectedConfig(),
    CmdDumpRegisters(),
    CmdEraseFlash(),
    CmdDisableDutyCycle(),
    CmdLBTRSSI(),
#endif

};

const verify_ptr_t CommandTerminal::_verify[NO_OF_COMMANDS] = {
    CmdDummy::verify,
    CmdDummy::verify,
    CmdDummy::verify,
    CmdDummy::verify,
    CmdDummy::verify,
    CmdDummy::verify,

    CmdDummy::verify,
    CmdDummy::verify,
    CmdDummy::verify,
    CmdDummy::verify,
    CmdDummy::verify,
    CmdSerialBaudRate::verify,
    CmdDebugBaudRate::verify,
    CmdStartUpMode::verify,

    CmdDefaultFrequencyBand::verify,
    CmdFrequencyBand::verify,
    CmdFrequencySubBand::verify,
    CmdPublicNetwork::verify,
    CmdDeviceId::verify,
    CmdDeviceClass::verify,
    CmdLbt::verify,

    CmdAppPort::verify,
    CmdNetworkAddress::verify,
    CmdNetworkSessionKey::verify,
    CmdDataSessionKey::verify,
    CmdUplinkCounter::verify,
    CmdDownlinkCounter::verify,
    CmdDummy::verify,
    CmdDummy::verify,
    CmdNetworkKey::verify,
    CmdNetworkId::verify,

    CmdJoinDelay::verify,
// Remove join settings commands until valid case for changing default settings
//    CmdJoinRx1Offset::verify,
//    CmdJoinRx2Datarate::verify,
//    CmdJoinRx2Frequency::verify,
    CmdJoinRequest::verify,
    CmdJoinRetries::verify,
    CmdJoinByteOrder::verify,
    CmdNetworkJoinMode::verify,
    CmdPreserveSession::verify,
    CmdDummy::verify,
    CmdDummy::verify,
    CmdLinkCheckCount::verify,
    CmdLinkCheckThreshold::verify,
    CmdEncryption::verify,

    CmdDummy::verify,
    CmdDummy::verify,
    CmdDummy::verify,

    CmdSessionDataRate::verify,
    CmdChannelMask::verify,

    CmdTxDataRate::verify,
    CmdTxPower::verify,
    CmdAntennaGain::verify,
    CmdTxFrequency::verify,
    CmdTxInverted::verify,
    CmdTxWait::verify,
    CmdTxChannel::verify,
    CmdTxNextMs::verify,
    CmdTimeOnAir::verify,

    CmdRxDelay::verify,
    CmdRxOutput::verify,
    CmdRxInverted::verify,

    CmdErrorCorrection::verify,
    CmdCRC::verify,
    CmdAdaptiveDataRate::verify,

    CmdACKAttempts::verify,
    CmdRepeat::verify,
    CmdMacCmd::verify,

    CmdSendString::verify,
    CmdSendBinary::verify,
    CmdReceiveOnce::verify,

    CmdDummy::verify,
    CmdDummy::verify,
    CmdSerialClearOnError::verify,
    CmdWakeMode::verify,
    CmdWakeInterval::verify,
    CmdWakePin::verify,
    CmdWakeDelay::verify,
    CmdWakeTimeout::verify,
    CmdDummy::verify,
    CmdLogLevel::verify,

    CmdDummy::verify,
    CmdRxDataRate::verify,
    CmdRxFrequency::verify,
    CmdReceiveContinuous::verify,
    CmdSendStringOnInterval::verify,

#ifdef MTS_RADIO_DEBUG_COMMANDS
    CmdDummy::verify,
    CmdSendContinuous::verify,
    CmdDummy::verify,
    CmdDummy::verify,
    CmdEraseFlash::verify,
    CmdDisableDutyCycle::verify,
    CmdLBTRSSI::verify,
#endif

};

const action_ptr_t CommandTerminal::_action[NO_OF_COMMANDS] = {
    CmdAttention::action,
    CmdIdentification::action,
    CmdResetCpu::action,
    CmdDummy::action,
    CmdDummy::action,
    CmdDummy::action,

    CmdFactoryDefault::action,
    CmdSaveConfig::action,
    CmdDisplayConfig::action,
    CmdDisplayStats::action,
    CmdResetStats::action,
    CmdSerialBaudRate::action,
    CmdDebugBaudRate::action,
    CmdStartUpMode::action,

    CmdDefaultFrequencyBand::action,
    CmdFrequencyBand::action,
    CmdFrequencySubBand::action,
    CmdPublicNetwork::action,
    CmdDeviceId::action,
    CmdDeviceClass::action,
    CmdLbt::action,

    CmdAppPort::action,
    CmdNetworkAddress::action,
    CmdNetworkSessionKey::action,
    CmdDataSessionKey::action,
    CmdUplinkCounter::action,
    CmdDownlinkCounter::action,
    CmdSaveSession::action,
    CmdRestoreSession::action,
    CmdNetworkKey::action,
    CmdNetworkId::action,

    CmdJoinDelay::action,
// Remove join settings commands until valid case for changing default settings
//    CmdJoinRx1Offset::action,
//    CmdJoinRx2Datarate::action,
//    CmdJoinRx2Frequency::action,
    CmdJoinRequest::action,
    CmdJoinRetries::action,
    CmdJoinByteOrder::action,
    CmdNetworkJoinMode::action,
    CmdPreserveSession::action,
    CmdNetworkJoinStatus::action,
    CmdNetworkLinkCheck::action,
    CmdLinkCheckCount::action,
    CmdLinkCheckThreshold::action,
    CmdEncryption::action,

    CmdRssi::action,
    CmdSnr::action,
    CmdDataPending::action,

    CmdSessionDataRate::action,
    CmdChannelMask::action,

    CmdTxDataRate::action,
    CmdTxPower::action,
    CmdAntennaGain::action,
    CmdTxFrequency::action,
    CmdTxInverted::action,
    CmdTxWait::action,
    CmdTxChannel::action,
    CmdTxNextMs::action,
    CmdTimeOnAir::action,

    CmdRxDelay::action,
    CmdRxOutput::action,
    CmdRxInverted::action,

    CmdErrorCorrection::action,
    CmdCRC::action,
    CmdAdaptiveDataRate::action,

    CmdACKAttempts::action,
    CmdRepeat::action,
    CmdMacCmd::action,

    CmdSendString::action,
    CmdSendBinary::action,
    CmdReceiveOnce::action,

    CmdDummy::action,
    CmdDummy::action,
    CmdSerialClearOnError::action,
    CmdWakeMode::action,
    CmdWakeInterval::action,
    CmdWakePin::action,
    CmdWakeDelay::action,
    CmdWakeTimeout::action,
    CmdPing::action,
    CmdLogLevel::action,

    CmdDummy::action,
    CmdRxDataRate::action,
    CmdRxFrequency::action,
    CmdReceiveContinuous::action,
    CmdSendStringOnInterval::action,

#ifdef MTS_RADIO_DEBUG_COMMANDS
    CmdDummy::action,
    CmdSendContinuous::action,
    CmdWriteProtectedConfig::action,
    CmdDumpRegisters::action,
    CmdEraseFlash::action,
    CmdDisableDutyCycle::action,
    CmdLBTRSSI::action,
#endif

};

CommandTerminal::CommandTerminal(mts::ATSerial& serial) :
  _serial(serial),
  _mode(mDot::COMMAND_MODE),
  _sleep_standby(true),
#if defined(TARGET_MTS_MDOT_F411RE)
  _xbee_on_sleep(XBEE_ON_SLEEP),
#else
  _xbee_on_sleep(GPIO2),
#endif
  _autoOTAEnabled(false)
{
    _serialp = &serial;
}

void CommandTerminal::init() {
    _dot->setEvents(_events);
}

void CommandTerminal::printHelp() {
    const char* name = NULL;
    const char* text = NULL;
    const char* desc = NULL;
    const char* tab = "\t";

    std::string header("Command");
    header.append(tab);
    header.append(tab);
    header.append("Name");
    header.append(tab);
    header.append(tab);
    header.append(tab);
    header.append("Description");

    write(newline);
    write(header.c_str());
    write(newline);
    write(newline);

    for (int i = 0; i < NO_OF_COMMANDS; i++) {
        name = _commands[i].name();
        text = _commands[i].text();
        desc = _commands[i].desc();
        write(text);
        if (strlen(text) < 8)
            write(tab);
        write(tab);
        write(name);
        if (strlen(name) < 8)
            write(tab);
        if (strlen(name) < 16)
            write(tab);
        write(tab);
        write(desc);
        write(newline);
    }

    write(newline);
}

bool CommandTerminal::writeable() {
    return _serialp->writeable();
}

bool CommandTerminal::readable() {
    return _serialp->readable();
}

char CommandTerminal::read() {
    char ch;
    _serialp->read(&ch, 1);
    return ch;
}

void CommandTerminal::write(const char* message) {
    while (!writeable())
        ;
    _serialp->write(message, strlen(message));
}

void CommandTerminal::writef(const char* format, ...) {
    char buff[256];

    va_list ap;
    va_start(ap, format);
    int size = vsnprintf(buff, 256, format, ap);
    while (!writeable())
        ;
    _serialp->write(buff, size);
    va_end(ap);
}

void CommandTerminal::serialLoop() {
    Timer serial_read_timer;
    std::vector<uint8_t> serial_buffer;
    std::vector<uint8_t> data;
    int timeout = 0;

    serial_read_timer.start();

    if (_dot->getStartUpMode() == mDot::SERIAL_MODE) {
        _xbee_on_sleep = GPIO_PIN_SET;

        timeout = _dot->getWakeDelay();

        // wait for timeout or start of serial data
        while (!readable() && serial_read_timer.read_ms() < timeout && !_serialp->escaped()) {
            osDelay(2);
        }
    }

    if (!readable() && _events->SendAck()) {
        logDebug("SERIAL NOT READABLE and ACK REQUESTED");
        goto L_SEND;
    }

    if (readable() && !_serialp->escaped()) {

        serial_read_timer.reset();
        timeout = _dot->getWakeTimeout();

        while (serial_read_timer.read_ms() < timeout && serial_buffer.size() <= _dot->getMaxPacketLength()) {
            while (readable() && serial_buffer.size() < _dot->getMaxPacketLength()) {
                serial_buffer.push_back(read());
                serial_read_timer.reset();

                if (_serialp->escaped())
                    break;
            }
        }

        serial_read_timer.stop(), serial_read_timer.reset();

        if (!serial_buffer.empty()) {
            if (_dot->getStartUpMode() == mDot::SERIAL_MODE)
                 _xbee_on_sleep = GPIO_PIN_RESET;

L_SEND:
            // wait for any duty cycle limit to expire
            while (_dot->getNextTxMs() > 0 && !_serialp->escaped()) {
                osDelay(10);
            }

            if (_dot->getIsIdle()) {
                logDebug("Received serial data, sending out radio.");

                if (_dot->send(serial_buffer, false) != mDot::MDOT_OK) {
                    logDebug("Send failed.");
                    // If the data should be tossed after send failure, clear buffer
                    if (_dot->getSerialClearOnError()) {
                        serial_buffer.clear();
                    }
                } else {

                    // wait for send to finish
                    while (!_dot->getIsIdle() && !_serialp->escaped()) {
                        osDelay(10);
                    }

                    // call recv to wait for any packet before sending again
                    if (!_serialp->escaped())
                        _dot->recv(data);

                    // Clear the serial buffer if send is success
                    serial_buffer.clear();

                    // In class C mode pending data will be sent automatically without uplink
                    if (_dot->getClass() != "C") {
                        if (_dot->getDataPending()) {
                            logDebug("Data is pending");
                            goto L_SEND;
                        }
                        if (_dot->getAckRequested()) {
                            logDebug("Ack requested");
                            goto L_SEND;
                        }
                    }
                }
            } else {
                logDebug("Radio is busy, cannot send.\r\n");
                osDelay(10);
            }

        } else {
            logDebug("No data received from serial to send.\r\n");
        }
    }

    if (!_serialp->readable() && _dot->getStartUpMode() == mDot::SERIAL_MODE && !_serialp->escaped()) {
        sleep(true);
    }

    if (_serialp->escaped()) {
        _serialp->clearEscaped();
        _serialp->rxClear();
        serial_data_mode = false;
        _mode = mDot::COMMAND_MODE;
        logDebug("Exit Serial Mode");
        write(done);
        return;
    }

    if (!_dot->getNetworkJoinStatus()) {
        serial_data_mode = false;
        _mode = mDot::COMMAND_MODE;
        logDebug("Exit Serial Mode");
        write(no_carrier);
        return;
    }
}

bool CommandTerminal::autoJoinCheck() {

    std::string escape_buffer;
    int sleep = 1000;
    Timer tmr;
    tmr.start();
    int cnt = 0;

    while (!_dot->getNetworkJoinStatus()) {
        if (!serial_data_mode) {
            write("\r\nJoining network... ");
        }
        logInfo("Joining network... ");

        if (_dot->getNextTxMs() > 0) {
            if (!serial_data_mode) {
                writef("\r\nWaiting %lu s before next join attempt\r\n", _dot->getNextTxMs() / 1000);
            }
            logInfo("Waiting %lu s before next join attempt", _dot->getNextTxMs() / 1000);

            tmr.reset();
            while (_dot->getNextTxMs() > 0 && !_serial.escaped()) {
                osDelay(20);
            }
        }

        if (!_serial.escaped() && _dot->joinNetworkOnce() == mDot::MDOT_OK) {
            if (!serial_data_mode) {
                write("Network Joined\r\n");
                write(done);
            }
            logInfo("Network Joined");
            return false;
        }

        if (!serial_data_mode) {
            write("Network Join failed\r\n");
            write(error);
        }
        logInfo("Network Join failed");

        if (!_serial.escaped() && _dot->getFrequencySubBand() != 0 && _dot->getJoinRetries() > 0 && cnt++ > _dot->getJoinRetries()) {
            cnt = 0;

            if (lora::ChannelPlan::IsPlanFixed(_dot->getFrequencyBand())) {
                uint8_t band = ((_dot->getFrequencySubBand()) % 8) + 1;
                logWarning("Join retries exhausted, switching to sub band %u", band);
                _dot->setFrequencySubBand(band);
            }
        }

        tmr.reset();
        while (tmr.read_ms() < sleep && !_serial.escaped()) {
            osDelay(10);
        }

        if (_serial.escaped()) {
            _serial.clearEscaped();
            serial_data_mode = false;
            _mode = mDot::COMMAND_MODE;
            if (!serial_data_mode) {
                write("Join Canceled\r\n");
                write(done);
            }
            logInfo("Join Canceled\r\n");
            return true;
        }
    }

    return false;
}

void CommandTerminal::start() {

    char ch;
    bool running = true;
    bool echo = _dot->getEcho();
    std::string command;
#if defined(TARGET_MTS_MDOT_F411RE)
    std::deque<std::string> history;
    int history_index = -1;
#endif
    std::vector<std::string> args;
    bool join_canceled = false;

    _autoOTAEnabled = _dot->getJoinMode() == mDot::AUTO_OTA;

    if (_dot->getStartUpMode() == mDot::SERIAL_MODE) {

        serial_data_mode = true;
        _mode = mDot::SERIAL_MODE;

        std::string escape_buffer;
        char ch;

        if (!_dot->getStandbyFlag()) {
            // wake up from power-on/reset

            int escape_timeout = 1000;
            Timer tmr;
            Timer escape_tmr;

            // wait one second for possible escape by user pressing '+' key
            tmr.reset();
            tmr.start();
            escape_tmr.reset();
            escape_tmr.start();
            while (tmr.read_ms() < escape_timeout) {
                if (_serial.readable()) {
                    _serial.read(&ch, 1);
                    escape_buffer += ch;
                }

                if (escape_buffer.find("+") != std::string::npos) {
                    logInfo("Escape detected");
                    join_canceled = true;
                    serial_data_mode = false;
                    _mode = mDot::COMMAND_MODE;
                    command.clear();
                    break;
                }

                if (escape_tmr.read_ms() > escape_timeout)
                    escape_buffer.clear();

                osDelay(1);
            }
        }

        if (_mode == mDot::SERIAL_MODE && !_dot->getNetworkJoinStatus() && _dot->getJoinMode() == mDot::OTA) {
            if (_dot->joinNetworkOnce() != mDot::MDOT_OK) {
                serial_data_mode = false;
                _mode = mDot::COMMAND_MODE;

                logWarning("Start Up Mode set to SERIAL_MODE, but join failed.");
                _serial.writef("Network Not Joined\r\n");
                _serial.writef(error);
            }
        }
    }

    if (_dot->getJoinMode() == mDot::PEER_TO_PEER) {
        peer_to_peer = true;
    } else {
        peer_to_peer = false;
    }

    //Run terminal session
    while (running) {

        // wait for input to reduce at command idle current
        while (!readable() || _mode == mDot::SERIAL_MODE) {
            if (!join_canceled && _autoOTAEnabled) {
                join_canceled = autoJoinCheck();
                if (join_canceled)
                    command.clear();
            }

            if (!_autoOTAEnabled || (!join_canceled && _autoOTAEnabled)) {
                switch (_mode) {
                    case mDot::SERIAL_MODE:
                        // signal wakeup, read serial and output to radio
                        serialLoop();
                        continue;
                        break;
                    default:
                        break;
                }
            }

            ch = '\0';

            wait(0.00001); // 10 us
            _serial.escaped();
        }

        // read characters
        if (readable()) {
            ch = read();

            if (ch == '\b' || ch == 0x7f) {
                if (!command.empty()) {
                    writef("\b \b");
                    command.erase(command.size() - 1);
                }
                continue;
            } else if (ch == 0x1b || ch == 0x09) {
                osDelay(20);
                // catch escape sequence, or tab
                char ch1 = 0x00, ch2 = 0x00;

                if (readable()) {
                    ch1 = read();
                    if (readable())
                        ch2 = read();

#if defined(TARGET_MTS_MDOT_F411RE)
                    if (ch1 == 0x5b && ch2 == 0x41) {
                        // up key
                        for (size_t i = 0; i < command.size() + 1; i++) {
                            writef("\b \b");
                        }
                        if (history.size() > 0) {
                            if (++history_index >= int(history.size() - 1))
                                history_index = history.size() - 1;

                            command = history[history_index];
                            writef("%s", history[history_index].c_str());
                        } else {
                            command.clear();
                        }
                    } else if (ch1 == 0x5b && ch2 == 0x42) {

                        // down key
                        for (size_t i = 0; i < command.size() + 1; i++) {
                            writef("\b \b");
                        }

                        if (--history_index < 0) {
                            history_index = -1;
                            command.clear();
                        } else {
                            command = history[history_index];
                            writef("%s", history[history_index].c_str());
                        }
                    }
#endif
                }
                while (readable())
                    read();
                continue;
            } else {
                command += ch;
            }

            // echo chars if enabled
            if (echo && !(ch == '\r' || ch == '\n'))
                writef("%c", ch);
        }

        // look for end of command line
        if (command.find("\n") != std::string::npos || command.find("\r") != std::string::npos) {
            // remove new line or cr character
            command.erase(command.size() - 1);
            write("\r"); // match standard modem output
            write(newline);
        } else {
            continue;
        }

        // trim whitespace from command
        mts::Text::trim(command, "\r\n\t ");

        if (command.size() < 1) {
            command.clear();
            continue;
        }

        // parse command and args
        args.clear();

        // find first '=' character
        size_t delim_index = command.find("=");
        if (delim_index != std::string::npos) {
            args.push_back(command.substr(0, delim_index));
        } else {
            // find first ' ' character
            delim_index = command.find(" ");
            if (delim_index != std::string::npos) {
                args.push_back(command.substr(0, delim_index));
            } else {
                args.push_back(command);
            }
        }

        if (delim_index != std::string::npos) {
            std::vector<std::string> params = mts::Text::split(command.substr(delim_index + 1), ",");
            args.insert(args.end(), params.begin(), params.end());
        }

        args[0] = mts::Text::toUpper(args[0]);

        // print help
        if ((args[0].find("?") == 0 || args[0].find("HELP") == 0) && args.size() == 1) {
            printHelp();
            command.clear();
        } else if ((args[0].find("ATE?") == 0 && args[0].length() == 4) || (args[0].find("ATE") == 0 && args[0].length() == 3)) {
            writef("%d\r\n", _dot->getEcho());
            write(done);
        } else if (args[0].find("ATE0") == 0 && args[0].length() == 4) {
            _dot->setEcho(false);
            write(done);
            echo = _dot->getEcho();
        } else if (args[0].find("ATE1") == 0 && args[0].length() == 4) {
            _dot->setEcho(true);
            write(done);
            echo = _dot->getEcho();
        } else if ((args[0].find("ATV?") == 0 && args[0].length() == 4) || (args[0].find("ATV") == 0 && args[0].length() == 3)) {
            writef("%d\r\n", _dot->getVerbose());
            write(done);
        } else if (args[0].find("ATV0") == 0 && args[0].length() == 4) {
            _dot->setVerbose(false);
            write(done);
        } else if (args[0].find("ATV1") == 0 && args[0].length() == 4) {
            _dot->setVerbose(true);
            write(done);
        } else if ((args[0].find("AT&K?") == 0 && args[0].length() == 5) || (args[0].find("AT&K") == 0 && args[0].length() == 4)) {
            writef("%d\r\n", (_dot->getFlowControl() ? 3 : 0));
            write(done);
        } else if (args[0].find("AT&K0") == 0 && args[0].length() == 5) {
            _dot->setFlowControl(false);
            write(done);
        } else if (args[0].find("AT&K3") == 0 && args[0].length() == 5) {
            _dot->setFlowControl(true);
            write(done);
        } else if (args[0] == "AT+SD") {
            if (_dot->getNetworkJoinStatus()) {
                logDebug("Enter Serial Mode");
                write(connect);
                serial_data_mode = true;
                _mode = mDot::SERIAL_MODE;
            } else {
                logDebug("Network Not Joined");
                write("Network Not Joined\r\n");
                write(error);
            }
        } else if (args[0] == "AT+SLEEP") {
            if (args.size() > 2 && (args[1] != "?")) {
                write("Invalid argument\r\n");
                write(error);
            } else {
                if (args.size() > 1 && args[1] == "?") {
                    write("(0:deepsleep,1:sleep)\r\n");
                    write(done);
                } else {
                    _sleep_standby = !(args.size() > 1 && args[1] == "1");
#if defined(TARGET_MTS_MDOT_F411RE)
                    //Read the board ID. If all 0's, it is revision B. This hardware does not support deep sleep.
                    DigitalIn ID2(PC_4);
                    DigitalIn ID1(PC_5);
                    DigitalIn ID0(PD_2);
                    if(ID2 == 0 && ID1 == 0 && ID0 == 0 && _sleep_standby == 1){
                        _sleep_standby = 0;
                        logWarning("This hardware version does not support deep sleep. Using sleep mode instead.");
                    }
#endif                    
                    write(done);
                    osDelay(5);
                    this->sleep(_sleep_standby);
                    osDelay(1);
                }
            }
        } else {
            bool found = false;
            bool query = false;

            std::string lookfor = args[0];

            // per command help
            if ((args[0].find("?") == 0 || args[0].find("HELP") == 0))
                lookfor = mts::Text::toUpper(args[1]);

            // trim off any trailing '?' and mark as a query command
            if (args[0].rfind("?") == args[0].length() - 1) {
                query = true;
                lookfor = args[0].substr(0, args[0].length() - 1);
            }

            // search for command
            for (int i = 0; i < NO_OF_COMMANDS; i++) {

                // match CMD or CMD? syntax if command is queryable
                if (lookfor == _commands[i].text() && (!query || (query && _commands[i].queryable()))) {
                    found = true;
                    if (args[0] == "HELP") {
                        writef("%s%s", _commands[i].help().c_str(), newline);
                        write(done);
                    }

                    else if (args.size() > 1 && args[1] == "?") {
                        writef("%s%s", _commands[i].usage().c_str(), newline);
                        write(done);
                    } else if (!_verify[i](args)) {
                        writef("%s%s", _errorMessage.c_str(), newline);
                        writef("%s", error);
                    } else {
                        if (_action[i](args) == 0) {
                            writef("%s", done);
                        } else {
                            writef("%s%s", _errorMessage.c_str(), newline);
                            writef("%s", error);
                        }
                    }
                }
            }

            if (!found) {
                writef("%s", command_error);
                writef("%s", error);
            }
        }

#if defined(TARGET_MTS_MDOT_F411RE)
        if (history.size() == 0 || history.front() != command)
            history.push_front(command);
        history_index = -1;
        command.clear();

        while (history.size() > 10)
            history.pop_back();
#else
        command.clear();
#endif
    }
}

void CommandTerminal::sleep(bool standby) {
    _xbee_on_sleep = GPIO_PIN_RESET;

    _serial.rxClear();
    _serial.txClear();

#if defined(TARGET_XDOT_L151CC)
    xdot_save_gpio_state();

    /* GPIO Ports Clock Enable */
    __GPIOA_CLK_ENABLE();
    __GPIOB_CLK_ENABLE();
    __GPIOC_CLK_ENABLE();
    __GPIOH_CLK_ENABLE();

    GPIO_InitTypeDef GPIO_InitStruct;

    // UART1_TX, UART1_RTS & UART1_CTS to analog nopull - RX could be a wakeup source
    GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_11 | GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // I2C_SDA & I2C_SCL to analog nopull
    GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    // SPI_MOSI, SPI_MISO, SPI_SCK, & SPI_NSS to analog nopull
    GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    // iterate through potential wake pins - leave the configured wake pin alone if one is needed
    if (_dot->getWakePin() != WAKE || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_0;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
    if (_dot->getWakePin() != GPIO0 || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_4;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
    if (_dot->getWakePin() != GPIO1 || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_5;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
    if (_dot->getWakePin() != GPIO2 || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_0;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    }
    if (_dot->getWakePin() != GPIO3 || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_2;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    }
    if (_dot->getWakePin() != UART1_RX || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
#else
    uint32_t portA[6];
    uint32_t portB[6];
    uint32_t portC[6];
    uint32_t portD[6];
    uint32_t portH[6];

    //Save the GPIO state.
    portA[0] = GPIOA->MODER;
    portA[1] = GPIOA->OTYPER;
    portA[2] = GPIOA->OSPEEDR;
    portA[3] = GPIOA->PUPDR;
    portA[4] = GPIOA->AFR[0];
    portA[5] = GPIOA->AFR[1];

    portB[0] = GPIOB->MODER;
    portB[1] = GPIOB->OTYPER;
    portB[2] = GPIOB->OSPEEDR;
    portB[3] = GPIOB->PUPDR;
    portB[4] = GPIOB->AFR[0];
    portB[5] = GPIOB->AFR[1];

    portC[0] = GPIOC->MODER;
    portC[1] = GPIOC->OTYPER;
    portC[2] = GPIOC->OSPEEDR;
    portC[3] = GPIOC->PUPDR;
    portC[4] = GPIOC->AFR[0];
    portC[5] = GPIOC->AFR[1];

    portD[0] = GPIOD->MODER;
    portD[1] = GPIOD->OTYPER;
    portD[2] = GPIOD->OSPEEDR;
    portD[3] = GPIOD->PUPDR;
    portD[4] = GPIOD->AFR[0];
    portD[5] = GPIOD->AFR[1];

    portH[0] = GPIOH->MODER;
    portH[1] = GPIOH->OTYPER;
    portH[2] = GPIOH->OSPEEDR;
    portH[3] = GPIOH->PUPDR;
    portH[4] = GPIOH->AFR[0];
    portH[5] = GPIOH->AFR[1];

    /* GPIO Ports Clock Enable */
    __GPIOA_CLK_ENABLE();
    __GPIOB_CLK_ENABLE();
    __GPIOC_CLK_ENABLE();

    GPIO_InitTypeDef GPIO_InitStruct;

    // Set port A pins to analog nopull
    GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_6 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 
                | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);    

    // Set port B pins to analog nopull
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_3 | GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); 

    // Set port C pins to analog nopull
    GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); 

    // iterate through potential wake pins - leave the configured wake pin alone if one is needed
    // XBEE_DIN - PA3
    // XBEE_DIO2 - PA5
    // XBEE_DIO3 - PA4
    // XBEE_DIO4 - PA7
    // XBEE_DIO5 - PC1
    // XBEE_DIO6 - PA1
    // XBEE_DIO7 - PA0
    // XBEE_SLEEPRQ - PA11
                
    if (_dot->getWakePin() != XBEE_DIN || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_3;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }

    if (_dot->getWakePin() != XBEE_DIO2 || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_5;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }

    if (_dot->getWakePin() != XBEE_DIO3 || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_4;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }

         if (_dot->getWakePin() != XBEE_DIO4 || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_7;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }

     if (_dot->getWakePin() != XBEE_DIO5 || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_1;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    }

     if (_dot->getWakePin() != XBEE_DIO6 || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_1;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }

     if (_dot->getWakePin() != XBEE_DIO7 || _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_0;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }

     if (_dot->getWakePin() != XBEE_SLEEPRQ|| _dot->getWakeMode() == mDot::RTC_ALARM) {
        GPIO_InitStruct.Pin = GPIO_PIN_11;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
 
#endif
    _dot->sleep(_dot->getWakeInterval(), _dot->getWakeMode(), standby);

#if defined(TARGET_XDOT_L151CC)
    xdot_restore_gpio_state();
#else
    //Restore the GPIO state.
    GPIOA->MODER = portA[0];
    GPIOA->OTYPER = portA[1];
    GPIOA->OSPEEDR = portA[2];
    GPIOA->PUPDR = portA[3];
    GPIOA->AFR[0] = portA[4];
    GPIOA->AFR[1] = portA[5];

    GPIOB->MODER = portB[0];
    GPIOB->OTYPER = portB[1];
    GPIOB->OSPEEDR = portB[2];
    GPIOB->PUPDR = portB[3];
    GPIOB->AFR[0] = portB[4];
    GPIOB->AFR[1] = portB[5];

    GPIOC->MODER = portC[0];
    GPIOC->OTYPER = portC[1];
    GPIOC->OSPEEDR = portC[2];
    GPIOC->PUPDR = portC[3];
    GPIOC->AFR[0] = portC[4];
    GPIOC->AFR[1] = portC[5];

    GPIOD->MODER = portD[0];
    GPIOD->OTYPER = portD[1];
    GPIOD->OSPEEDR = portD[2];
    GPIOD->PUPDR = portD[3];
    GPIOD->AFR[0] = portD[4];
    GPIOD->AFR[1] = portD[5];

    GPIOH->MODER = portH[0];
    GPIOH->OTYPER = portH[1];
    GPIOH->OSPEEDR = portH[2];
    GPIOH->PUPDR = portH[3];
    GPIOH->AFR[0] = portH[4];
    GPIOH->AFR[1] = portH[5];
#endif

    _serial.rxClear();
    _serial.txClear();
}

std::string CommandTerminal::formatPacketData(const std::vector<uint8_t>& data, const uint8_t& format) {
    if (format == mDot::HEXADECIMAL)
        return mts::Text::bin2hexString(data);
    else
        return std::string(data.begin(), data.end());
}

bool CommandTerminal::waitForEscape(int timeout, mDot* dot, WaitType wait) {
    Timer timer;

    timer.start();
    while (timer.read_ms() < timeout) {

        if (dot != NULL) {
            if (wait == WAIT_SEND && (!dot->getIsTransmitting())) {
                return false;
            }
        }

        if (_serialp != NULL && _serialp->escaped()) {
            _serialp->clearEscaped();
            return true;
        }

        osDelay(10);
    }

    return false;
}

void CommandTerminal::wakeup(void) {
}

void CommandTerminal::RadioEvent::PacketRx(uint8_t port, uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr, lora::DownlinkControl ctrl, uint8_t slot, uint8_t retries) {
    mDotEvent::PacketRx(port, payload, size, rssi, snr, ctrl, slot, retries,0,0);

    if (serial_data_mode) {
        logDebug("Rx %d bytes", size);
        if (size > 0) {
            CommandTerminal::Serial()->write((char*) RxPayload, RxPayloadSize);
        }
        if (!CommandTerminal::Serial()->readable() && _dot->getAckRequested() && _dot->getClass() == "C") {
            _sendAck = true;
        }
    }
}

CommandTerminal::~CommandTerminal() {
    delete _events;
}