diff --git a/src/DUNE/Hardware/BasicModem.cpp b/src/DUNE/Hardware/BasicModem.cpp index 7ee1a7f1a8..297bbd4162 100644 --- a/src/DUNE/Hardware/BasicModem.cpp +++ b/src/DUNE/Hardware/BasicModem.cpp @@ -45,8 +45,6 @@ namespace DUNE { namespace Hardware { - //! Default command timeout. - static const double c_timeout = 5.0; //! Default input line termination. static std::string c_line_term_in = "\r\n"; //! Default output line termination. @@ -176,52 +174,31 @@ namespace DUNE { { Concurrency::ScopedMutex l(m_mutex); + + if (m_read_mode == mode) + return; + m_read_mode = mode; } - if (mode == READ_MODE_LINE) + switch (mode) { - if (m_bytes.size() > 0) - { // if have bytes in queue, transfer to lines - getTask()->spew("There are %d bytes in the queue bytes. Convert to queue of lines.", - m_bytes.size()); - while (m_bytes.size() > 0) - { - uint8_t byte = 0; - m_bytes.pop(byte); - m_chars.push(byte); - } - std::string line = ""; - while (!m_chars.empty()) - { - if (!processInput(line)) - continue; - - if (line.empty()) - continue; - - if (!handleUnsolicited(line)) - { - m_lines.push(line); - line = ""; - } - } - } - } - else + case READ_MODE_LINE: { - if (m_lines.size()) - { - getTask()->war("[BasicModem]:There are %d lines in the queue. Convert to queue of bytes.", - m_lines.size()); - while (m_lines.size() > 0) - { - std::string line = m_lines.pop(); - getTask()->war("[BasicModem]:line: %s", line.c_str()); - for (size_t i = 0; i < line.size(); ++i) - m_bytes.push(line[i]); - } - } + if (!converBytesToLines()) + break; + + std::string line; + handleIncomingCharacters(line); + break; + } + + case READ_MODE_RAW: + convertLinesToBytes(); + break; + + default: + break; } } @@ -295,6 +272,8 @@ namespace DUNE bool BasicModem::processInput(std::string& str) { + Concurrency::ScopedMutex l(m_ingestion_mtx); + bool got_line = false; while (!m_chars.empty()) @@ -368,7 +347,14 @@ namespace DUNE std::string BasicModem::readLine(Time::Counter& timer) { - if (m_lines.waitForItems(timer.getRemaining())) + const auto remaining = timer.getRemaining(); + if (remaining <= 0) + { + getTask()->war("[BasicModem]:timeout while reading line"); + throw ReadTimeout(); + } + + if (m_lines.waitForItems(remaining)) { std::string line = m_lines.pop(); if (line != m_last_cmd) @@ -386,6 +372,96 @@ namespace DUNE m_handle->flushInput(); } + void + BasicModem::handleIncomingCharacters(std::string& str) + { + while (!incomingCharsQueueEmpty()) + { + if (!processInput(str)) + continue; + + if (str.empty()) + continue; + + if (!handleUnsolicited(str)) + pushLine(str); + + str = ""; + } + } + + void + BasicModem::ingestIncomingDataRaw(const char* data, const size_t len) + { + Concurrency::ScopedMutex l(m_ingestion_mtx); + for (size_t i = 0; i < len; ++i) + m_bytes.push(data[i]); + } + + void + BasicModem::ingestIncomingDataLine(const char* data, const size_t len) + { + Concurrency::ScopedMutex l(m_ingestion_mtx); + for (size_t i = 0; i < len; ++i) + m_chars.push(data[i]); + } + + void + BasicModem::pushLine(const std::string& line) + { + Concurrency::ScopedMutex l(m_ingestion_mtx); + m_lines.push(line); + } + + bool + BasicModem::incomingCharsQueueEmpty(void) + { + Concurrency::ScopedMutex l(m_ingestion_mtx); + return m_chars.empty(); + } + + bool + BasicModem::converBytesToLines(void) + { + Concurrency::ScopedMutex l(m_ingestion_mtx); + + if (m_bytes.size() <= 0) + return false; + + // if have bytes in queue, transfer to lines + getTask()->spew("There are %u bytes in the queue bytes. Convert to queue of lines.", + m_bytes.size()); + while (m_bytes.size() > 0) + { + uint8_t byte = 0; + m_bytes.pop(byte); + m_chars.push(byte); + } + + return true; + } + + bool + BasicModem::convertLinesToBytes(void) + { + Concurrency::ScopedMutex l(m_ingestion_mtx); + + if (m_lines.size() <= 0) + return false; + + getTask()->war("[BasicModem]:There are %u lines in the queue. Convert to queue of bytes.", + m_lines.size()); + while (m_lines.size() > 0) + { + std::string line = m_lines.pop(); + getTask()->war("[BasicModem]:line: %s", line.c_str()); + for (const auto& c: line) + m_bytes.push(c); + } + + return true; + } + void BasicModem::run(void) { @@ -401,7 +477,9 @@ namespace DUNE try { rv = m_handle->read(bfr, sizeof(bfr)); - } catch (std::runtime_error &e) { + } + catch (std::runtime_error& e) + { m_task->war("[BasicModem]:%s: %s", Status::getString(Status::CODE_IO_ERROR), e.what()); break; } @@ -417,32 +495,27 @@ namespace DUNE break; } - if (getReadMode() == READ_MODE_RAW) + const auto mode = getReadMode(); + + switch (mode) + { + case READ_MODE_RAW: { - for (size_t i = 0; i < rv; ++i) - m_bytes.push(bfr[i]); + ingestIncomingDataRaw(bfr, rv); + break; } - else + + case READ_MODE_LINE: { bfr[rv] = 0; m_task->spew("%s", Streams::sanitize(bfr).c_str()); - - for (size_t i = 0; i < rv; ++i) - { - m_chars.push(bfr[i]); - } - - while (!m_chars.empty()) - { - if (!processInput(line)) - continue; - - if (line.empty()) - continue; - - if (!handleUnsolicited(line)) - m_lines.push(line); - } + ingestIncomingDataLine(bfr, rv); + handleIncomingCharacters(line); + break; + } + + default: + break; } } } diff --git a/src/DUNE/Hardware/BasicModem.hpp b/src/DUNE/Hardware/BasicModem.hpp index 4c0be1d86d..5197b8c324 100644 --- a/src/DUNE/Hardware/BasicModem.hpp +++ b/src/DUNE/Hardware/BasicModem.hpp @@ -102,6 +102,9 @@ namespace DUNE setBusy(bool value); protected: + //! Default command timeout. + static constexpr double c_timeout = 5.0; + //! Read mode. enum ReadMode { @@ -113,6 +116,8 @@ namespace DUNE //! Concurrency lock. Concurrency::Mutex m_mutex; + //! Lock for data ingestion. + Concurrency::Mutex m_ingestion_mtx; //! Handle unsolicited or asynchronous commands. //! @param[in] str command string. @@ -223,6 +228,29 @@ namespace DUNE //! True to trim white-space. bool m_line_trim; + + + void + handleIncomingCharacters(std::string& str); + + void + ingestIncomingDataRaw(const char* data, const size_t len); + + void + ingestIncomingDataLine(const char* data, const size_t len); + + void + pushLine(const std::string& line); + + bool + incomingCharsQueueEmpty(void); + + bool + converBytesToLines(void); + + bool + convertLinesToBytes(void); + bool processInput(std::string& str); diff --git a/src/DUNE/Hardware/HayesModem.cpp b/src/DUNE/Hardware/HayesModem.cpp index 0c4eabd230..0947302ce1 100644 --- a/src/DUNE/Hardware/HayesModem.cpp +++ b/src/DUNE/Hardware/HayesModem.cpp @@ -157,6 +157,36 @@ namespace DUNE return str; } + std::string + HayesModem::readValue(const std::string& cmd, const std::string& rly, const double tmt) + { + sendAT(cmd); + std::string str = waitForReply(rly, tmt); + expectOK(); + return str; + } + + std::string + HayesModem::waitForReply(const std::string& rly, const double tmt) + { + Time::Counter timer(tmt); + std::string str; + + do + { + str = readLine(timer); + + if (Utils::String::startsWith(str, rly)) + return str; + } + while(!timer.overflow()); + + if (str.empty()) + throw ReadTimeout(); + else + throw UnexpectedReply(rly, str); + } + void HayesModem::sendAT(const std::string& str) { @@ -175,11 +205,22 @@ namespace DUNE } void - HayesModem::expect(const std::string& str) + HayesModem::expect(const std::string& str, const bool persistent) { - std::string rv = readLine(); - if (rv != str) - throw UnexpectedReply(str, rv); + Time::Counter timer(getTimeout()); + + do + { + std::string rv = readLine(timer); + if (rv == str) + return; + else if (!persistent || isErrorReply(rv)) + throw UnexpectedReply(str, rv); + } + while (!timer.overflow()); + + getTask()->war("[BasicModem]:timeout while reading line"); + throw ReadTimeout(); } void @@ -193,5 +234,17 @@ namespace DUNE { expect("READY"); } + + bool + HayesModem::isErrorReply(const std::string& str) + { + return m_error_replies.find(str) != m_error_replies.end(); + } + + void + HayesModem::addErrorReply(const std::string& str) + { + m_error_replies.insert(str); + } } } diff --git a/src/DUNE/Hardware/HayesModem.hpp b/src/DUNE/Hardware/HayesModem.hpp index f5814834f2..3f976836f7 100644 --- a/src/DUNE/Hardware/HayesModem.hpp +++ b/src/DUNE/Hardware/HayesModem.hpp @@ -32,6 +32,7 @@ // ISO C++ 98 headers. #include +#include // DUNE headers. #include @@ -108,7 +109,7 @@ namespace DUNE sendRaw(const uint8_t* data, unsigned data_size); void - expect(const std::string& str); + expect(const std::string& str, const bool persistent = true); void expectREADY(void); @@ -129,9 +130,27 @@ namespace DUNE std::string readValue(const std::string& cmd); + std::string + readValue(const std::string& cmd, const std::string& rly, const double tmt = c_timeout); + + //! Wait for a specific reply. + //! @param[in] rly expected reply start. + //! @param[in] tmt maximum time to wait for a reply. + //! @return full reply string. + std::string + waitForReply(const std::string& rly, const double tmt = c_timeout); + + void + addErrorReply(const std::string& str); + private: //! Last RSSI value. IMC::RSSI m_rssi; + //! List of error command responses. + std::unordered_set m_error_replies; + + bool + isErrorReply(const std::string& str); }; } } diff --git a/src/Transports/Evologics/Task.cpp b/src/Transports/Evologics/Task.cpp index a4973d7bea..86eaee6aa2 100644 --- a/src/Transports/Evologics/Task.cpp +++ b/src/Transports/Evologics/Task.cpp @@ -90,6 +90,8 @@ namespace Transports unsigned src_level_underwater; //! Name of the section with modem addresses. std::string addr_section; + //! Driver timeout. + double driver_timeout; }; // Type definition for mapping addresses. @@ -234,6 +236,13 @@ namespace Transports .defaultValue("Evologics Addresses") .description("Name of the configuration section with modem addresses"); + param("Driver Timeout", m_args.driver_timeout) + .minimumValue("0.0") + .defaultValue("5.0") + .units(Units::Second) + .description("Driver timeout for command responses. If value is 0, " + "the driver won't wait for responses."); + m_medium.medium = IMC::VehicleMedium::VM_UNKNOWN; bind(this); @@ -295,6 +304,9 @@ namespace Transports { m_sound_speed = m_args.sound_speed_def; processEntityForSoundSpeed(); + + if (paramChanged(m_args.driver_timeout) && m_driver) + m_driver->setDriverTimeout(m_args.driver_timeout); } void @@ -341,6 +353,7 @@ namespace Transports m_driver = new Driver(this, m_sock); m_driver->setLineTermIn("\r\n"); m_driver->setLineTermOut("\n"); + m_driver->setDriverTimeout(m_args.driver_timeout); } void @@ -372,7 +385,10 @@ namespace Transports } if (m_simulating) - m_driver->setDriverTimeout(c_sim_timeout); + { + applyEntityParameter(m_args.driver_timeout, c_sim_timeout); + m_driver->setDriverTimeout(m_args.driver_timeout); + } if (!isActive()) requestActivation(); diff --git a/src/Transports/IridiumSBD/Driver.hpp b/src/Transports/IridiumSBD/Driver.hpp index 5e239ac38e..7bcdc1bc90 100644 --- a/src/Transports/IridiumSBD/Driver.hpp +++ b/src/Transports/IridiumSBD/Driver.hpp @@ -48,9 +48,15 @@ namespace Transports using DUNE_NAMESPACES; //! Default AT command timeout. - static const double c_timeout = 5.0; + static const double c_at_cmd_timeout = 5.0; //! Maximum number of revision lines. static const unsigned c_max_rev_lines = 10; + //! Default timeout for CSQ command. + static constexpr double c_default_timeout_csq = 7.0; + //! Default timeout for SBDIX command. + static constexpr double c_default_timeout_sbdix = 20.0; + //! Error reply string. + static constexpr char* c_error_reply = "ERROR"; class Driver: public HayesModem { @@ -67,6 +73,7 @@ namespace Transports m_use_9523 = use_9523N; m_wait_boot = wait_boot; setLineTrim(true); + addErrorReply(c_error_reply); m_rssi_wdog.setTop(rssi_time_check); } @@ -92,7 +99,7 @@ namespace Transports unsigned getMOMSN(void) { - std::string value = readValue("+SBDS"); + std::string value = readValue("+SBDS", "+SBDS", c_at_cmd_timeout); unsigned momsn = 0; if (std::sscanf(value.c_str(), "+SBDS:%*u,%u,%*u,%*u", &momsn) != 1) throw DUNE::Hardware::InvalidFormat(value); @@ -189,12 +196,14 @@ namespace Transports else writeBufferMO(&data[0], data.size()); + std::string sbdix; + if (alert_reply) - sendAT("+SBDIXA"); + sbdix = readValue("+SBDIXA", "+SBDIX", c_default_timeout_sbdix); else - sendAT("+SBDIX"); + sbdix = readValue("+SBDIX", "+SBDIX", c_default_timeout_sbdix); - setBusy(true); + handleSBDIX(sbdix); } //! Retrieve the result of the last SBD session. The function @@ -222,18 +231,14 @@ namespace Transports void clearBufferMO(void) { - std::string rv = readValue("+SBDD0"); - if (rv != "0") - throw std::runtime_error(DTR("error ocurred while clearing MO buffer")); + clearMessageBuffer(BFR_TYPE_ORIGINATED); } //! Clear MT SBD message buffer. void clearBufferMT(void) { - std::string rv = readValue("+SBDD1"); - if (rv != "0") - throw std::runtime_error(DTR("error ocurred while clearing MT buffer")); + clearMessageBuffer(BFR_TYPE_TERMINATED); } //! Check if a ring alert was received. @@ -268,6 +273,14 @@ namespace Transports return readValue("V"); } + //! Set modem driver timeout + //! @param[in] timeout time to wait (seconds). + void + setDriverTimeout(double timeout) + { + setTimeout(timeout); + } + private: //! Message buffer types. enum BufferType @@ -332,8 +345,6 @@ namespace Transports handleCIEV(str); else if (String::startsWith(str, "+AREG")) handleAREG(str); - else if (String::startsWith(str, "+SBDIX")) - handleSBDIX(str); else return false; @@ -390,10 +401,6 @@ namespace Transports m_length_msg_9523 = m_session_result.getLengthMT(); } } - - setSkipLine("OK"); - - setBusy(false); } //! Enable or disable radio activity. @@ -444,17 +451,13 @@ namespace Transports void clearMessageBuffer(BufferType type) { - std::string rv = readValue(String::str("+SBDD%u", type)); - if (rv != "0") - throw std::runtime_error(DTR("error ocurred while clearing buffer")); + readValue(String::str("+SBDD%u", type), "0", c_at_cmd_timeout); } void clearSequenceNumber(void) { - std::string rv = readValue("+SBDC"); - if (rv != "0") - throw std::runtime_error(DTR("error ocurred while clearing the MOMSN")); + readValue("+SBDC", "0", c_at_cmd_timeout); } void @@ -476,7 +479,7 @@ namespace Transports { if (data_size == 0) { - clearMessageBuffer(BFR_TYPE_ORIGINATED); + clearBufferMO(); return; } @@ -578,12 +581,7 @@ namespace Transports if (!m_rssi_wdog.overflow()) return; - sendAT("+CSQ"); - - // Needs a timeout bigger than the default 5 seconds. - Counter timer(7.0); - std::string val = readLine(timer); - expectOK(); + std::string val = readValue("+CSQ", "+CSQ", c_default_timeout_csq); unsigned rssi = 0; if (std::sscanf(val.c_str(), "+CSQ:%u", &rssi) != 1) diff --git a/src/Transports/IridiumSBD/Task.cpp b/src/Transports/IridiumSBD/Task.cpp index 6ae3203b0d..e22054434e 100644 --- a/src/Transports/IridiumSBD/Task.cpp +++ b/src/Transports/IridiumSBD/Task.cpp @@ -98,6 +98,8 @@ namespace Transports size_t queue_max; //! Timeout in seconds for the general monitor double general_monitor_timeout; + //! Driver timeout. + double driver_timeout; }; struct Task: public DUNE::Tasks::Task @@ -234,6 +236,13 @@ namespace Transports .minimumValue("60.0") .description("Timeout in seconds for the general monitor"); + param("Driver Timeout", m_args.driver_timeout) + .minimumValue("0.0") + .defaultValue("5.0") + .units(Units::Second) + .description("Driver timeout for command responses. If value is 0, " + "the driver won't wait for responses."); + bind(this); bind(this); bind(this); @@ -287,6 +296,9 @@ namespace Transports trace("Setting general monitor timeout to %f seconds", m_args.general_monitor_timeout); m_general_monitor.setTop(m_args.general_monitor_timeout); } + + if (paramChanged(m_args.driver_timeout) && m_driver) + m_driver->setDriverTimeout(m_args.driver_timeout); } //! Acquire resources. @@ -661,7 +673,7 @@ namespace Transports } bool - receptionSequence() + receptionSequence(void) { if (m_driver->hasRingAlert()) m_driver->checkMailBoxAlert(); @@ -685,7 +697,7 @@ namespace Transports } bool - transmissionSequence() + transmissionSequence(void) { if (m_tx_request != NULL) {