few changes for RTS/CTS control
Dependencies: MTS-Serial libmDot mbed-rtos mbed
Fork of mDot_AT_firmware by
CommandTerminal/CommandTerminal.cpp
- Committer:
- serg838
- Date:
- 2016-10-25
- Revision:
- 11:77b7911263da
- Parent:
- 9:ff62b20f7000
File content as of revision 11:77b7911263da:
#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; }