MultiTech / Mbed OS mDot_AT_firmware_CUSTOM

Dependencies:   libmDot-Custom MTS-Serial

Fork of mDot_AT_firmware_CUSTOM by Jason Reiss

To change channel plans replace AS923 with AU915, EU868, KR920 or US915 on line 15

#define CHANNEL_PLAN CP_AS923

See Supported Channel Plans

Revision:
15:36db31c18231
Parent:
12:98445fa50cd3
Child:
18:4e02a7a01625
--- a/CommandTerminal/CommandTerminal.cpp	Mon Aug 29 10:05:41 2016 -0500
+++ b/CommandTerminal/CommandTerminal.cpp	Wed Aug 31 11:57:27 2016 -0500
@@ -1,1083 +1,1123 @@
-#include "ctype.h"
-#include "CommandTerminal.h"
-#include "Command.h"
-#include "MTSLog.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(),
-
-    CmdFrequencyBand(),
-    CmdFrequencySubBand(),
-    CmdPublicNetwork(),
-    CmdDeviceId(),
-    CmdDeviceClass(),
-
-    CmdAppPort(),
-    CmdNetworkAddress(),
-    CmdNetworkSessionKey(),
-    CmdDataSessionKey(),
-    CmdUplinkCounter(),
-    CmdDownlinkCounter(),
-    CmdSaveSession(),
-    CmdRestoreSession(),
-    CmdNetworkKey(),
-    CmdNetworkId(),
-
-    CmdJoinDelay(),
-    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(),
-};
-
-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,
-
-    CmdFrequencyBand::verify,
-    CmdFrequencySubBand::verify,
-    CmdPublicNetwork::verify,
-    CmdDeviceId::verify,
-    CmdDeviceClass::verify,
-
-    CmdAppPort::verify,
-    CmdNetworkAddress::verify,
-    CmdNetworkSessionKey::verify,
-    CmdDataSessionKey::verify,
-    CmdUplinkCounter::verify,
-    CmdDownlinkCounter::verify,
-    CmdDummy::verify,
-    CmdDummy::verify,
-    CmdNetworkKey::verify,
-    CmdNetworkId::verify,
-
-    CmdJoinDelay::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,
-};
-
-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,
-
-    CmdFrequencyBand::action,
-    CmdFrequencySubBand::action,
-    CmdPublicNetwork::action,
-    CmdDeviceId::action,
-    CmdDeviceClass::action,
-
-    CmdAppPort::action,
-    CmdNetworkAddress::action,
-    CmdNetworkSessionKey::action,
-    CmdDataSessionKey::action,
-    CmdUplinkCounter::action,
-    CmdDownlinkCounter::action,
-    CmdSaveSession::action,
-    CmdRestoreSession::action,
-    CmdNetworkKey::action,
-    CmdNetworkId::action,
-
-    CmdJoinDelay::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,
-
-};
-
-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),
-  _idle_thread(idle, NULL, osPriorityLow)
-{
-    _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 (_dot->getFrequencyBand() == mDot::FB_US915 || _dot->getFrequencyBand() == mDot::FB_AU915 ) {
-                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;
-    std::deque<std::string> history;
-    int history_index = -1;
-    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 (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());
-                        }
-                    }
-                }
-                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");
-                    write(done);
-                    this->sleep(_sleep_standby);
-                    wait(0.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 (history.size() == 0 || history.front() != command)
-            history.push_front(command);
-        history_index = -1;
-        command.clear();
-
-        while (history.size() > 10)
-            history.pop_back();
-    }
-}
-
-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);
-    }
-#endif
-
-    _dot->sleep(_dot->getWakeInterval(), _dot->getWakeMode(), standby);
-
-#if defined(TARGET_XDOT_L151CC)
-    xdot_restore_gpio_state();
-#endif
-}
-
-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, retries);
-
-    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;
-}
-
+#include "ctype.h"
+#include "CommandTerminal.h"
+#include "Command.h"
+#include "MTSLog.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(),
+
+    CmdFrequencyBand(),
+    CmdFrequencySubBand(),
+    CmdPublicNetwork(),
+    CmdDeviceId(),
+    CmdDeviceClass(),
+
+    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(),
+#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,
+
+    CmdFrequencyBand::verify,
+    CmdFrequencySubBand::verify,
+    CmdPublicNetwork::verify,
+    CmdDeviceId::verify,
+    CmdDeviceClass::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,
+#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,
+
+    CmdFrequencyBand::action,
+    CmdFrequencySubBand::action,
+    CmdPublicNetwork::action,
+    CmdDeviceId::action,
+    CmdDeviceClass::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,
+#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),
+  _idle_thread(idle, NULL, osPriorityLow)
+{
+    _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 (_dot->getFrequencyBand() == mDot::FIXED ) {
+                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;
+    std::deque<std::string> history;
+    int history_index = -1;
+    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 (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());
+                        }
+                    }
+                }
+                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");
+                    write(done);
+                    this->sleep(_sleep_standby);
+                    wait(0.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 (history.size() == 0 || history.front() != command)
+            history.push_front(command);
+        history_index = -1;
+        command.clear();
+
+        while (history.size() > 10)
+            history.pop_back();
+    }
+}
+
+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);
+    }
+#endif
+
+    _dot->sleep(_dot->getWakeInterval(), _dot->getWakeMode(), standby);
+
+#if defined(TARGET_XDOT_L151CC)
+    xdot_restore_gpio_state();
+#endif
+}
+
+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, retries);
+
+    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;
+}