dev

Dependencies:   MTS-Serial libmDot-mbed5

Fork of Dot-AT-Firmware by MultiTech

CommandTerminal/CommandTerminal.cpp

Committer:
Mike Fiore
Date:
2016-04-04
Revision:
9:ff62b20f7000
Parent:
6:e27eaad36a0c
Child:
11:05e0f30c03a6

File content as of revision 9:ff62b20f7000:

#include "ctype.h"
#include "CommandTerminal.h"
#include "Command.h"
#include "MTSLog.h"
#include <cstdarg>
#include <deque>

const char CommandTerminal::banner[] = "\r\n\nMultiTech Systems LoRa XBee Module\r\n\n";
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;

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

void CommandTerminal::addCommand(Command* cmd) {
    _commands.push_back(cmd);
}

CommandTerminal::CommandTerminal(mts::ATSerial& serial, mDot* dot)
:
  _serial(serial),
  _dot(dot),
  _events(new RadioEvent(serial)),
  _mode(mDot::COMMAND_MODE),
  _idle_thread(idle, NULL, osPriorityLow),
  _sleep_standby(true),
  _xbee_on_sleep(XBEE_ON_SLEEP) {

    _dot->setEvents(_events);
    _dot->setWakeupCallback(this, &CommandTerminal::wakeup);
    _serialp = &serial;

    addCommand(new CmdAttention(_dot));
    addCommand(new CmdIdentification(_dot, serial));
    addCommand(new CmdResetCpu(_dot, serial));
    addCommand(new CmdDummy(_dot, "Enable/Disable Echo", "ATE", "ATE0: disable, ATE1: enable"));
    addCommand(new CmdDummy(_dot, "Enable/Disable Verbose", "ATV", "ATV0: disable, ATV1: enable"));
    addCommand(new CmdDummy(_dot, "Hardware Flow Control", "AT&K", "AT&K0: disable, AT&K3: enable"));

    addCommand(new CmdFactoryDefault(_dot));
    addCommand(new CmdSaveConfig(_dot));
    addCommand(new CmdDisplayConfig(_dot, serial));
    addCommand(new CmdDisplayStats(_dot, serial));
    addCommand(new CmdResetStats(_dot, serial));
    addCommand(new CmdSerialBaudRate(_dot, serial));
    addCommand(new CmdDebugBaudRate(_dot, serial));
    addCommand(new CmdStartUpMode(_dot, serial));

    addCommand(new CmdFrequencyBand(_dot, serial));
    addCommand(new CmdFrequencySubBand(_dot, serial));
    addCommand(new CmdPublicNetwork(_dot, serial));
    addCommand(new CmdDeviceId(_dot, serial));
    addCommand(new CmdDeviceClass(_dot, serial));

    addCommand(new CmdAppPort(_dot, serial));
    addCommand(new CmdNetworkAddress(_dot, serial));
    addCommand(new CmdNetworkSessionKey(_dot, serial));
    addCommand(new CmdDataSessionKey(_dot, serial));
    addCommand(new CmdUplinkCounter(_dot, serial));
    addCommand(new CmdDownlinkCounter(_dot, serial));
    addCommand(new CmdSaveSession(_dot, serial));
    addCommand(new CmdRestoreSession(_dot, serial));
    addCommand(new CmdNetworkKey(_dot, serial));
    addCommand(new CmdNetworkId(_dot, serial));

    addCommand(new CmdJoinDelay(_dot, serial));
    addCommand(new CmdJoinRequest(_dot, serial));
    addCommand(new CmdJoinRetries(_dot, serial));
    addCommand(new CmdJoinByteOrder(_dot, serial));
    addCommand(new CmdNetworkJoinMode(_dot, serial));
    addCommand(new CmdPreserveSession(_dot, serial));
    addCommand(new CmdNetworkJoinStatus(_dot, serial));
    addCommand(new CmdNetworkLinkCheck(_dot, serial));
    addCommand(new CmdLinkCheckCount(_dot, serial));
    addCommand(new CmdLinkCheckThreshold(_dot, serial));
    addCommand(new CmdEncryption(_dot, serial));

    addCommand(new CmdRssi(_dot, serial));
    addCommand(new CmdSnr(_dot, serial));
    addCommand(new CmdDataPending(_dot, serial));

    addCommand(new CmdSessionDataRate(_dot, serial));

    addCommand(new CmdTxDataRate(_dot, serial));
    addCommand(new CmdTxPower(_dot, serial));
    addCommand(new CmdAntennaGain(_dot, serial));
    addCommand(new CmdTxFrequency(_dot, serial));
    addCommand(new CmdTxInverted(_dot, serial));
    addCommand(new CmdTxWait(_dot, serial));
    addCommand(new CmdTxChannel(_dot, serial));
    addCommand(new CmdTxNextMs(_dot, serial));
    addCommand(new CmdTimeOnAir(_dot, serial));

    addCommand(new CmdRxDelay(_dot, serial));
    addCommand(new CmdRxOutput(_dot, serial));
    addCommand(new CmdRxInverted(_dot, serial));

    addCommand(new CmdErrorCorrection(_dot, serial));
    addCommand(new CmdCRC(_dot, serial));
    addCommand(new CmdAdaptiveDataRate(_dot, serial));

    addCommand(new CmdACKAttempts(_dot, serial));
    addCommand(new CmdRepeat(_dot, serial));

    addCommand(new CmdSendString(_dot, serial));
    addCommand(new CmdSendBinary(_dot, serial));
    addCommand(new CmdReceiveOnce(_dot, serial));

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

    addCommand(new CmdDummy(_dot, "***** Test Commands *****", "", ""));
    addCommand(new CmdRxDataRate(_dot, serial));
    addCommand(new CmdRxFrequency(_dot, serial));
    addCommand(new CmdReceiveContinuous(_dot, serial));
    addCommand(new CmdSendStringOnInterval(_dot, serial));
}

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 (std::vector<Command*>::iterator it = _commands.begin(); it != _commands.end(); ++it) {
        name = (*it)->name();
        text = (*it)->text();
        desc = (*it)->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 _serial.writeable();
}

bool CommandTerminal::readable() {
    return _serial.readable();
}

char CommandTerminal::read() {
    char ch;
    _serial.read(&ch, 1);
    return ch;
}

void CommandTerminal::write(const char* message) {
    while (!writeable())
        ;
    _serial.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())
        ;
    _serial.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 && !_serial.escaped()) {
            osDelay(2);
        }
    }

    if (readable() && !_serial.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 (_serial.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;

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

            if (!_dot->getIsTransmitting()) {
                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->getIsTransmitting() && !_serial.escaped())
                        osDelay(10);

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

                    // Clear the serial buffer if send is success
                    serial_buffer.clear();
                }
            } else {
                logDebug("Radio is busy, cannot send.\r\n");
                osDelay(10);
            }

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

    if (!_serial.readable() && _dot->getStartUpMode() == mDot::SERIAL_MODE && !_serial.escaped()) {
        _xbee_on_sleep = GPIO_PIN_RESET;
        sleep(_sleep_standby);
    }

    if (_serial.escaped()) {
        _serial.clearEscaped();
        _serial.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()) {
        write("\r\nJoining network... ");

        if (_dot->getNextTxMs() > 0) {
            int rand_time = rand() % 10000;
            writef("\r\nWaiting %lu s before next join attempt\r\n", (_dot->getNextTxMs() + rand_time) / 1000);

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

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

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

        write("Network Join failed\r\n");
        write(error);

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

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

            if (sleep < 60 * 60 * 1000)
                sleep *= 2;
        }

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

        if (_serial.escaped()) {
            _serial.clearEscaped();
            serial_data_mode = false;
            _mode = mDot::COMMAND_MODE;
            write("Join Canceled\r\n");
            write(done);
            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;

    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 && _dot->getJoinMode() == mDot::AUTO_OTA) {
                join_canceled = autoJoinCheck();
                if (join_canceled)
                    command.clear();
            }

            if (_dot->getJoinMode() != mDot::AUTO_OTA || (!join_canceled && _dot->getJoinMode() == mDot::AUTO_OTA)) {
                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 (std::vector<Command*>::iterator it = _commands.begin(); it != _commands.end() && !found; ++it) {
                Command* cmd = *it;

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

                    else if (args.size() > 1 && args[1] == "?") {
                        writef("%s%s", cmd->usage().c_str(), newline);
                        write(done);
                    } else if (!cmd->verify(args)) {
                        writef("%s%s", cmd->errorMessage().c_str(), newline);
                        writef("%s", error);
                    } else {
                        if (cmd->action(args) == 0) {
                            writef("%s", done);
                        } else {
                            writef("%s%s", cmd->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();
    }
}

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());
}

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

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

    _dot->sleep(_dot->getWakeInterval(), _dot->getWakeMode(), standby);
}

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) {
    if (_dot->getWakePin() == XBEE_DIN) {
        _serial.reattach(XBEE_DOUT, XBEE_DIN);
        logInfo("Wakeup pin was serial input");
    }
}

void CommandTerminal::RadioEvent::MacEvent(LoRaMacEventFlags* flags, LoRaMacEventInfo* info) {

    if (mts::MTSLog::getLogLevel() == mts::MTSLog::TRACE_LEVEL) {
        std::string msg = "OK";
        switch (info->Status) {
            case LORAMAC_EVENT_INFO_STATUS_ERROR:
                msg = "ERROR";
                break;
            case LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT:
                msg = "TX_TIMEOUT";
                break;
            case LORAMAC_EVENT_INFO_STATUS_RX_TIMEOUT:
                msg = "RX_TIMEOUT";
                break;
            case LORAMAC_EVENT_INFO_STATUS_RX_ERROR:
                msg = "RX_ERROR";
                break;
            case LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL:
                msg = "JOIN_FAIL";
                break;
            case LORAMAC_EVENT_INFO_STATUS_DOWNLINK_FAIL:
                msg = "DOWNLINK_FAIL";
                break;
            case LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL:
                msg = "ADDRESS_FAIL";
                break;
            case LORAMAC_EVENT_INFO_STATUS_MIC_FAIL:
                msg = "MIC_FAIL";
                break;
            default:
                break;
        }
        logTrace("Event: %s", msg.c_str());

        logTrace("Flags Tx: %d Rx: %d RxData: %d RxSlot: %d LinkCheck: %d JoinAccept: %d",
                 flags->Bits.Tx, flags->Bits.Rx, flags->Bits.RxData, flags->Bits.RxSlot, flags->Bits.LinkCheck, flags->Bits.JoinAccept);
        logTrace("Info: Status: %d ACK: %d Retries: %d TxDR: %d RxPort: %d RxSize: %d RSSI: %d SNR: %d Energy: %d Margin: %d Gateways: %d",
                 info->Status, info->TxAckReceived, info->TxNbRetries, info->TxDatarate, info->RxPort, info->RxBufferSize,
                 info->RxRssi, info->RxSnr, info->Energy, info->DemodMargin, info->NbGateways);
    }

    if (flags->Bits.Rx) {
        if (serial_data_mode) {
            logDebug("Rx %d bytes", info->RxBufferSize);
            if (info->RxBufferSize > 0) {
                _serial.write((char*) info->RxBuffer, info->RxBufferSize);
            }
        }

        delete[] info->RxBuffer;
    }
}

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