~aleteoryx/muditaos

78a52c0705a67889de5c71ff80d3150c9a006892 — Adam Dobrowolski 5 years ago 5995cfa
[EGD-5451] Added SMS Center SMSC check to OS

Extended cmd mechanism to provide class defined responses and
implemented SMS center verification to check if SIM card is properly
cofigured and is able to send SMS messagess at all.
This check is needed because if SMS Center is lost we are not able
to send SMS messages with no verbose error codes.
Added milliseconds and noexcept specifiers for at commands
Fixed Chanel name to Channel
M module-apps/application-settings-new/models/QuotesRepository.hpp => module-apps/application-settings-new/models/QuotesRepository.hpp +2 -1
@@ 1,7 1,7 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 11,6 11,7 @@
#include <list>
#include <functional>
#include <module-apps/Application.hpp>
#include <vfs.hpp>

namespace app
{

M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +2 -0
@@ 36,7 36,9 @@ set(SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcResponse.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcFactory.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/Commands.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/Cmd.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/response.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/CSCA.cpp
        )

if(NOT ${PROJECT_TARGET} STREQUAL "TARGET_Linux")

M module-cellular/Modem/ATCommon.cpp => module-cellular/Modem/ATCommon.cpp +26 -27
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ATCommon.hpp"
#include "time/ScopedTime.hpp"
#include <functional>
#include <log/log.hpp>
#include <string>


@@ 12,18 13,18 @@

using namespace at;

const std::string Chanel::OK         = "OK";
const std::string Chanel::ERROR      = "ERROR";
const std::string Chanel::NO_CARRIER = "NO CARRIER";
const std::string Chanel::BUSY       = "BUSY";
const std::string Chanel::NO_ANSWER  = "NO ANSWER";
const std::string Chanel::CME_ERROR  = "+CME ERROR:";
const std::string Chanel::CMS_ERROR  = "+CMS ERROR:";
// const std::string Chanel::CONNECT = "CONNECT";
// const std::string Chanel::RING = "RING";
// const std::string Chanel::NO_DIALTONE = "NO DIALTONE";

Result::Code Chanel::at_check(const std::vector<std::string> &arr)
const std::string Channel::OK         = "OK";
const std::string Channel::ERROR      = "ERROR";
const std::string Channel::NO_CARRIER = "NO CARRIER";
const std::string Channel::BUSY       = "BUSY";
const std::string Channel::NO_ANSWER  = "NO ANSWER";
const std::string Channel::CME_ERROR  = "+CME ERROR:";
const std::string Channel::CMS_ERROR  = "+CMS ERROR:";
// const std::string Channel::CONNECT = "CONNECT";
// const std::string Channel::RING = "RING";
// const std::string Channel::NO_DIALTONE = "NO DIALTONE";

Result::Code Channel::at_check(const std::vector<std::string> &arr)
{
    if (arr.size()) {
        for (auto el : arr) {


@@ 38,7 39,7 @@ Result::Code Chanel::at_check(const std::vector<std::string> &arr)
    return Result::Code::NONE;
}

bool Chanel::at_check_cmx_error(const std::string &CMX, const std::vector<std::string> &arr, uint32_t &errcode)
bool Channel::at_check_cmx_error(const std::string &CMX, const std::vector<std::string> &arr, uint32_t &errcode)
{
    if (arr.size()) {
        for (auto cmxerr : arr) {


@@ 54,7 55,7 @@ bool Chanel::at_check_cmx_error(const std::string &CMX, const std::vector<std::s
    return false;
}

void Chanel::cmd_log(std::string cmd, const Result &result, uint32_t timeout)
void Channel::cmd_log(std::string cmd, const Result &result, uint32_t timeout)
{
    cmd.erase(std::remove(cmd.begin(), cmd.end(), '\r'), cmd.end());
    cmd.erase(std::remove(cmd.begin(), cmd.end(), '\n'), cmd.end());


@@ 80,7 81,7 @@ void Chanel::cmd_log(std::string cmd, const Result &result, uint32_t timeout)
#endif
}

std::string Chanel::formatCommand(const std::string &cmd) const
std::string Channel::formatCommand(const std::string &cmd) const
{
    bool isTerminatorValid = std::find(validTerm.begin(), validTerm.end(), cmd.back()) != validTerm.end();
    if (isTerminatorValid) {


@@ 89,7 90,7 @@ std::string Chanel::formatCommand(const std::string &cmd) const
    return cmd + cmdSeparator;
}

class Result Chanel::cmd(const std::string cmd, uint32_t timeout, size_t rxCount)
Result Channel::cmd(const std::string &cmd, std::chrono::milliseconds timeout, size_t rxCount)
{
    Result result;
    blockedTaskHandle = xTaskGetCurrentTaskHandle();


@@ 99,10 100,10 @@ class Result Chanel::cmd(const std::string cmd, uint32_t timeout, size_t rxCount
    cmd_send(cmdFixed);

    uint32_t currentTime   = cpp_freertos::Ticks::GetTicks();
    uint32_t timeoutNeeded = ((timeout == UINT32_MAX) ? UINT32_MAX : currentTime + timeout);
    uint32_t timeoutNeeded = ((timeout.count() == UINT32_MAX) ? UINT32_MAX : currentTime + timeout.count());
    uint32_t timeElapsed   = currentTime;

    while (1) {
    while (true) {
        if (timeoutNeeded != UINT32_MAX && timeElapsed >= timeoutNeeded) {
            result.code = Result::Code::TIMEOUT;
            break;


@@ 110,7 111,7 @@ class Result Chanel::cmd(const std::string cmd, uint32_t timeout, size_t rxCount

        auto ret    = ulTaskNotifyTake(pdTRUE, timeoutNeeded - timeElapsed);
        timeElapsed = cpp_freertos::Ticks::GetTicks();
        if (ret) {
        if (ret != 0u) {
            std::vector<std::string> ret = cmd_receive();

            result.response.insert(std::end(result.response), std::begin(ret), std::end(ret));


@@ 161,22 162,20 @@ class Result Chanel::cmd(const std::string cmd, uint32_t timeout, size_t rxCount

    blockedTaskHandle = nullptr;
    cmd_post();
    cmd_log(cmdFixed, result, timeout);
    cmd_log(cmdFixed, result, timeout.count());

    return result;
}

auto Chanel::cmd(at::Cmd &at) -> Result
auto Channel::cmd(const at::Cmd &at) -> Result
{
    at.last.requested = cpp_freertos::Ticks::GetTicks();
    Result result     = this->cmd(at.cmd, at.timeout);
    at.last.response  = cpp_freertos::Ticks::GetTicks();
    at.last.status    = result.code;
    return result;
    auto time = utils::time::Scoped("Time to run at command" + at.getCmd());
    return cmd(at.getCmd(), at.getTimeout());
}

auto Chanel::cmd(const at::AT at) -> Result
auto Channel::cmd(const at::AT &at) -> Result
{
    auto cmd = at::factory(at);
    auto time = utils::time::Scoped("Time to run at command" + cmd.getCmd());
    return this->cmd(cmd);
}

M module-cellular/Modem/ATCommon.hpp => module-cellular/Modem/ATCommon.hpp +22 -21
@@ 3,24 3,25 @@

#pragma once

#include <string>
#include <vector>

#include <FreeRTOS.h>
#include <at/ErrorCode.hpp>
#include <at/Commands.hpp>
#include <at/Result.hpp>
#include <mutex.hpp>
#include <task.h>
#include "BaseChannel.hpp"

namespace sys
{
    class Service;
}

namespace at
{
    class Chanel
    class Channel : public BaseChannel
    {
      protected:
        constexpr static char cmdSeparator  = '\r';
        const std::array<char, 3> validTerm = {cmdSeparator, ',', '='};
        std::string formatCommand(const std::string &cmd) const;
        [[nodiscard]] auto formatCommand(const std::string &cmd) const -> std::string;

        cpp_freertos::MutexStandard mutex;
        TaskHandle_t blockedTaskHandle = nullptr;


@@ 33,26 34,26 @@ namespace at
        static const std::string NO_ANSWER;
        static const std::string CME_ERROR;
        static const std::string CMS_ERROR;
        // /// other codes unused right now: Please see quectel QuectelEC2526EC21ATCommandsManualV1.3 page 21
        // const std::string Chanel::CONNECT = "CONNECT";
        // const std::string Chanel::RING = "RING";
        // const std::string Chanel::NO_DIALTONE = "NO DIALTONE";
        // other codes unused right now: Please see quectel QuectelEC2526EC21ATCommandsManualV1.3 page 21
        // const std::string Channel::CONNECT = "CONNECT";
        // const std::string Channel::RING = "RING";
        // const std::string Channel::NO_DIALTONE = "NO DIALTONE";

        /// waits till ok or timeout
        virtual auto cmd(const std::string cmd, uint32_t timeout = at::default_timeout, size_t rxCount = 0)
            -> Result final;
        virtual auto cmd(const at::AT at) -> Result final;
        virtual auto cmd(at::Cmd &at) -> Result final;
        virtual auto cmd(const std::string &cmd,
                         std::chrono::milliseconds timeout = at::default_timeout,
                         size_t rxCount                    = 0) -> Result final;
        virtual auto cmd(const at::AT &at) -> Result final;
        virtual auto cmd(const at::Cmd &at) -> Result final;
        /// check for OK, ERROR in string in last token
        virtual Result::Code at_check(const std::vector<std::string> &arr);
        /// check for +CME ERROR: errors if exists in last token
        virtual bool at_check_cmx_error(const std::string &CMX, const std::vector<std::string> &arr, uint32_t &errcode);
        /// log result
        virtual void cmd_log(std::string cmd, const Result &result, uint32_t timeout) final;

        virtual void cmd_init()                        = 0;
        virtual void cmd_send(std::string cmd)         = 0;
        virtual std::vector<std::string> cmd_receive() = 0;
        virtual void cmd_post()                        = 0;
        virtual auto ProcessNewData(sys::Service *service) -> int
        {
            return 0;
        }
    };

}; // namespace at

M module-cellular/Modem/ATParser.hpp => module-cellular/Modem/ATParser.hpp +1 -1
@@ 17,7 17,7 @@ namespace bsp
    class Cellular;
}

class ATParser : public at::Chanel
class ATParser : public at::Channel
{
  public:
    enum class Urc

A module-cellular/Modem/BaseChannel.hpp => module-cellular/Modem/BaseChannel.hpp +37 -0
@@ 0,0 1,37 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <string>
#include <vector>
#include <at/Commands.hpp>
#include <at/Result.hpp>

namespace at
{
    class BaseChannel
    {
      public:
        /// @defgroup Channel OS dependent methods
        /// {

        /// waits till ok or timeout
        virtual auto cmd(const std::string &cmd,
                         std::chrono::milliseconds timeout = at::default_timeout,
                         size_t rxCount                    = 0) -> Result = 0;
        virtual auto cmd(const at::AT &at) -> Result   = 0;
        virtual auto cmd(const at::Cmd &at) -> Result  = 0;
        /// }

        /// @defgroup Channel Platform independent methods
        /// {
        virtual void cmd_log(std::string cmd, const Result &result, uint32_t timeout)
        {}
        virtual void cmd_init()                        = 0;
        virtual void cmd_send(std::string cmd)         = 0;
        virtual std::vector<std::string> cmd_receive() = 0;
        virtual void cmd_post()                        = 0;
        /// }
    };
} // namespace at

M module-cellular/Modem/TS0710/DLC_channel.h => module-cellular/Modem/TS0710/DLC_channel.h +1 -1
@@ 17,7 17,7 @@
#include <FreeRTOS.h>
#include <task.h>

class DLC_channel : public at::Chanel
class DLC_channel : public at::Channel
{
  public:
    using Callback_t = std::function<void(std::string &data)>;

A module-cellular/at/Cmd.hpp => module-cellular/at/Cmd.hpp +90 -0
@@ 0,0 1,90 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <string>
#include <Result.hpp>
#include <chrono>

namespace at
{
    using namespace std::chrono_literals;

    struct Cmd;

    /// if you've checked it's ok - or it was at least 300 in code somewhere, take this
    const auto default_timeout = 300ms;

    namespace cmd
    {
        enum class Modifier
        {
            None,
            Get,
            Set
        };
    }

    /// at::Cmd structure with command, it's timeout and some runtime data
    /// { command, timeout, last : {sent, response, status } }
    struct Cmd
    {
      private:
        std::string cmd;                                     /// command head to run (AT, CLCC etc...)
        std::chrono::milliseconds timeout = default_timeout; /// timeout for this command
      protected:
        std::unique_ptr<Result> result;          /// lifetime result storage to be able to return reference to it
        cmd::Modifier mod = cmd::Modifier::None; /// modifier responsible to define action we want to perform
        std::string body;                        /// part after command name
      public:
        Cmd() = delete;
        Cmd(std::string cmd, cmd::Modifier mod, std::chrono::milliseconds timeout = default_timeout) noexcept;
        Cmd(std::string cmd, std::chrono::milliseconds timeout = default_timeout) noexcept;
        Cmd(const Cmd &p) noexcept;
        Cmd(Cmd &&) noexcept;
        auto operator  =(const Cmd &) noexcept -> Cmd &;
        auto operator  =(Cmd &&) noexcept -> Cmd &;
        virtual ~Cmd() = default;

        auto setModifier(cmd::Modifier mod) noexcept
        {
            this->mod = mod;
        }

        [[nodiscard]] auto getCmd() const
        {
            switch (mod) {
            case cmd::Modifier::Get:
                return cmd + "?";
            case cmd::Modifier::Set:
                return cmd + "=" + body;
            case cmd::Modifier::None:
                return cmd;
            }
            return cmd;
        }

        [[nodiscard]] auto getTimeout() const noexcept
        {
            return timeout;
        }

        /// not the prettiest, for now it's ok - for commands which modify strings to execute - return copy of command
        /// str
        [[nodiscard]] operator std::string() const
        {
            return getCmd();
        }

        auto operator+(const std::string &val) const -> Cmd
        {
            Cmd tmp = *this;
            tmp.cmd += val;
            return tmp;
        }

        /// would have used optional but it doesn't work with references
        [[nodiscard]] virtual auto parse(Result &base_result) -> Result &;
    };
}; // namespace at

M module-cellular/at/Commands.hpp => module-cellular/at/Commands.hpp +29 -53
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 10,46 10,19 @@
#include <map>
#include <memory>
#include <string>
#include "Cmd.hpp"
#include "cmd/CSCA.hpp"
#include <chrono>

namespace at
{
    inline const uint32_t default_timeout = 5000; /// if unsure - take this
    inline const uint32_t default_doc_timeout =
        300; /// if you've checked it's ok - or it was at least 300 in code somewhere, take this
    inline const uint32_t default_long_doc_timeout = 15000;

    using namespace std::chrono_literals;
    const auto default_doc_timeout      = 5000ms; /// if unsure - take this
    const auto default_long_doc_timeout = 15000ms;

    /// at::Cmd structure with command, it's timeout and some runtime data
    /// { command, timeout, last : {sent, response, status } }
    struct Cmd
    {
        std::string cmd;                    /// command to run
        uint32_t timeout = default_timeout; /// timeout for this command
        struct
        {
            Result::Code status = Result::Code::NONE; /// last response for that command
            time_t requested    = 0;                  /// last time comand was requested
            time_t response     = 0;                  /// last time command was received
            auto request_time() -> time_t
            {
                return response - requested;
            }   /// time it took to send command and get response
        } last; /// last status of command execution

        Cmd(std::string cmd, uint32_t timeout = default_timeout) : cmd(std::move(cmd)), timeout(timeout)
        {}
        /// not the prettiest, for now it's ok - for commands which modify strings to execute - return copy of command
        /// str
        operator std::string() const
        {
            return cmd;
        }
        auto operator+(const std::string &val) const -> Cmd
        {
            Cmd tmp = *this;
            tmp.cmd += val;
            return tmp;
        }
    };

    /// doc is: per Quectel_EC25&EC21_AT_Commands_Manual_V1.3.pdf
    enum class AT


@@ 151,22 124,23 @@ namespace at
        COLP_GET,
        COLP_ENABLE,
        COLP_DISABLE,
        CSSN,   /// Supplementary Services - Supplementary Service Notifications
        QICSGP, /// Configure Parameters of a TCP/IP Context
        QIACT,  /// Activate a PDP Context
        QIDEACT /// Deactivate a PDP Context
        CSSN,    /// Supplementary Services - Supplementary Service Notifications
        QICSGP,  /// Configure Parameters of a TCP/IP Context
        QIACT,   /// Activate a PDP Context
        QIDEACT, /// Deactivate a PDP Context
        CSCA,    /// check SMS Center
    };

    // below timeouts are defined in Quectel_EC25&EC21_AT_Commands_Manual_V1.3.pdf
    inline auto factory(AT at) -> const Cmd &
    {
        static const std::map<AT, const Cmd> fact{
            {AT::AT, {"AT", 100}},
            {AT::AT, {"AT", 100ms}},
            {AT::ECHO_OFF, {"ATE0"}},
            {AT::FACTORY_RESET, {"AT&F"}},
            {AT::SW_INFO, {"ATI\r", default_doc_timeout}},
            {AT::FLOW_CTRL_ON, {"AT+IFC=2,2\r\n", 500}},
            {AT::FLOW_CTRL_OFF, {"AT+IFC=0,0", 500}},
            {AT::FLOW_CTRL_ON, {"AT+IFC=2,2\r\n", 500ms}},
            {AT::FLOW_CTRL_OFF, {"AT+IFC=0,0", 500ms}},
            {AT::URC_NOTIF_CHANNEL, {"AT+QCFG=\"cmux/urcport\",1"}},
            {AT::RI_PIN_AUTO_CALL, {"AT+QCFG=\"urc/ri/ring\",\"auto\""}},
            {AT::RI_PIN_OFF_CALL, {"AT+QCFG=\"urc/ri/ring\",\"off\""}},


@@ 178,11 152,11 @@ namespace at
            {AT::AT_PIN_READY_LOGIC, {"AT+QCFG=\"apready\",1,1,200"}},
            {AT::URC_NOTIF_SIGNAL, {"AT+QINDCFG=\"csq\",1"}},
            {AT::CRC_ON, {"AT+CRC=1"}},
            {AT::CALLER_NUMBER_PRESENTATION, {"AT+CLIP=1", 18000}},
            {AT::CALLER_NUMBER_PRESENTATION, {"AT+CLIP=1", 18000ms}},
            {AT::SMS_TEXT_FORMAT, {"AT+CMGF=1"}},
            {AT::SMS_UCSC2, {"AT+CSCS=\"UCS2\""}},
            {AT::SMS_GSM, {"AT+CSCS=\"GSM\""}},
            {AT::QSCLK_ON, {"AT+QSCLK=1", 3000}},
            {AT::QSCLK_ON, {"AT+QSCLK=1", 3000ms}},
            {AT::QDAI, {"AT+QDAI?"}},
            {AT::QDAI_INIT, {"AT+QDAI=1,0,0,5,0,1"}},
            {AT::SET_URC_CHANNEL, {"AT+QCFG=\"cmux/urcport\",2", default_doc_timeout}},


@@ 191,9 165,9 @@ namespace at
            {AT::CMGD, {"AT+CMGD=", default_doc_timeout}},
            {AT::CNUM, {"AT+CNUM"}},
            {AT::CIMI, {"AT+CIMI"}},
            {AT::QCMGR, {"AT+QCMGR=", 2000}},
            {AT::QCMGR, {"AT+QCMGR=", 2000ms}},
            {AT::ATH, {"ATH"}},
            {AT::ATA, {"ATA", 90000}},
            {AT::ATA, {"ATA", 90000ms}},
            {AT::ATD, {"ATD"}},
            {AT::IPR, {"AT+IPR="}},
            {AT::CMUX, {"AT+CMUX="}},


@@ 202,11 176,11 @@ namespace at
            {AT::CFUN_MIN_FUNCTIONALITY, {"AT+CFUN=0", default_long_doc_timeout}},
            {AT::CFUN_FULL_FUNCTIONALITY, {"AT+CFUN=1", default_long_doc_timeout}},
            {AT::CFUN_DISABLE_TRANSMITTING, {"AT+CFUN=4", default_long_doc_timeout}},
            {AT::CMGS, {"AT+CMGS=\""}},
            {AT::QCMGS, {"AT+QCMGS=\""}},
            {AT::CMGS, {"AT+CMGS=\"", 120s}},   // verified in docs
            {AT::QCMGS, {"AT+QCMGS=\"", 120s}}, // verified in docs
            {AT::CREG, {"AT+CREG?", default_doc_timeout}},
            {AT::QNWINFO, {"AT+QNWINFO"}},
            {AT::COPS, {"AT+COPS", 180000}},
            {AT::COPS, {"AT+COPS", 180000ms}},
            {AT::QSIMSTAT, {"AT+QSIMSTAT?"}},
            {AT::SIM_DET, {"AT+QSIMDET?"}},
            {AT::SIM_DET_ON, {"AT+QSIMDET=1,0"}},


@@ 222,7 196,7 @@ namespace at
            {AT::CUSD_OPEN_SESSION, {"AT+CUSD=1"}},
            {AT::CUSD_CLOSE_SESSION, {"AT+CUSD=2"}},
            {AT::CUSD_SEND, {"AT+CUSD=1,"}},
            {AT::SET_SMS_STORAGE, {"AT+CPMS=\"SM\",\"SM\",\"SM\"", 300}},
            {AT::SET_SMS_STORAGE, {"AT+CPMS=\"SM\",\"SM\",\"SM\"", 300ms}},
            {AT::CPIN, {"AT+CPIN=", default_timeout}},
            {AT::GET_CPIN, {"AT+CPIN?", default_timeout}},
            {AT::QPINC, {"AT+QPINC=", default_timeout}},


@@ 253,20 227,22 @@ namespace at
            {AT::COLP_DISABLE, {"AT+COLP=0", default_long_doc_timeout}},
            {AT::CSSN, {"AT+CSSN=\"", default_doc_timeout}},
            {AT::QICSGP, {"AT+QICSGP", default_timeout}},
            {AT::QIACT, {"AT+QIACT", 150000}},
            {AT::QIDEACT, {"AT+QIDEACT", 40000}}};
            {AT::QIACT, {"AT+QIACT", 150000ms}},
            {AT::QIDEACT, {"AT+QIDEACT", 40000ms}}};

        if (fact.count(at)) {
        if (fact.count(at) != 0u) {
            return fact.at(at);
        }
        LOG_ERROR("NO SUCH AT COMMAND DEFINED: %d", static_cast<int>(at));
        return fact.at(AT::AT);
    }

    enum class commadsSet
    {
        modemInit,
        simInit,
        smsInit
    };

    std::vector<AT> getCommadsSet(commadsSet set);
}; // namespace at

M module-cellular/at/ErrorCode.hpp => module-cellular/at/ErrorCode.hpp +1 -1
@@ 127,4 127,4 @@ namespace magic_enum
        static constexpr int max = at::MAX_AT_ERROR_VALUE + 2;
    };

} // namespace magic_enum
\ No newline at end of file
} // namespace magic_enum

M module-cellular/at/Result.hpp => module-cellular/at/Result.hpp +11 -7
@@ 16,12 16,14 @@ namespace at
        /// result class for AT send -> receive command, could return promise :p
        enum class Code
        {
            OK,      // at OK
            ERROR,   // at ERROR
            TIMEOUT, // at Timeout
            TOKENS,  // at numbers of tokens needed met
            NONE,    // no code
        } code = Code::NONE;
            OK,            /// at OK
            ERROR,         /// at ERROR
            TIMEOUT,       /// at Timeout
            TOKENS,        /// at numbers of tokens needed met
            NONE,          /// no code
            UNDEFINED,     /// undefined result - usage of Undefined result, define and pin result to use it
            PARSING_ERROR, /// parser error
        } code = Code::UNDEFINED;

        //! Information about Equipment or Network error as variant type
        /*!


@@ 34,9 36,11 @@ namespace at

        std::vector<std::string> response;

        explicit operator bool() const
        virtual explicit operator bool() const
        {
            return code == Code::OK;
        }

        virtual ~Result() = default;
    };
} // namespace at

A module-cellular/at/cmd/CSCA.hpp => module-cellular/at/cmd/CSCA.hpp +41 -0
@@ 0,0 1,41 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "Result.hpp"
#include <at/Cmd.hpp>
#include <functional>
#include <PhoneNumber.hpp>

namespace at
{
    namespace result
    {
        /// please see documentation:
        /// QuectelEC2526EC21ATCommandsManualV13.1100970659
        /// page: 118 for more information
        struct CSCA : public Result
        {
            std::string smsCenterAddress;
            std::string smsTypeOfAddress;
            explicit CSCA(const Result &);
            operator bool() const override;
        };
    } // namespace result

    namespace cmd
    {

        class PhoneView;

        class CSCA : public Cmd
        {
          public:
            CSCA() noexcept;
            explicit CSCA(at::cmd::Modifier mod) noexcept;
            [[nodiscard]] auto parse(Result &base_result) -> result::CSCA & final;
            void set(const utils::PhoneNumber::View &smsCenterAddress, int smsTypeOfAddress);
        };
    } // namespace cmd
} // namespace at

A module-cellular/at/cmd/src/CSCA.cpp => module-cellular/at/cmd/src/CSCA.cpp +71 -0
@@ 0,0 1,71 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <log/log.hpp>
#include <memory>
#include <string>
#include <type_traits>
#include <at/cmd/CSCA.hpp>

namespace at
{
    namespace cmd
    {
        CSCA::CSCA(at::cmd::Modifier mod) noexcept : Cmd("AT+CSCA", mod)
        {}

        CSCA::CSCA() noexcept : CSCA(at::cmd::Modifier::None)
        {}

        result::CSCA &CSCA::parse(Result &base_result)
        {
            auto *p = new result::CSCA(base_result);
            result  = std::unique_ptr<result::CSCA>(p);
            // use parent operator bool
            if (p->Result::operator bool()) {
                if (p->response.empty()) {
                    LOG_ERROR("Can't parse - empty response");
                    p->code = result::CSCA::Code::PARSING_ERROR;
                }
                else {
                    auto str = p->response[0];
                    str.erase(std::remove(str.begin(), str.end(), ' '), str.end());
                    auto pos            = str.find(":");
                    auto end            = str.find(",");
                    p->smsCenterAddress = std::string(str, pos + 1, end - pos - 1);
                    p->smsTypeOfAddress = std::string(str, end + 1);
                    if (p->smsCenterAddress.empty() || p->smsTypeOfAddress.empty()) {
                        LOG_ERROR("Can't parse - bad value");
                        p->code = result::CSCA::Code::PARSING_ERROR;
                    }
                }
            }
            return *p;
        }

        void CSCA::set(const utils::PhoneNumber::View &smsCenterAddress, int smsTypeOfAddress)
        {
            body += "\"" + smsCenterAddress.getE164() + "\"," + std::to_string(smsTypeOfAddress);
        }
    } // namespace cmd

    namespace result
    {
        CSCA::CSCA(const Result &that) : Result(that)
        {}

        CSCA::operator bool() const
        {
            if (!Result::operator bool()) {
                return false;
            }
            if (smsCenterAddress.empty() || smsCenterAddress == "\"\"") {
                return false;
            }
            if (smsTypeOfAddress.empty() || smsTypeOfAddress == "\"\"") {
                return false;
            }
            return true;
        }
    } // namespace result
} // namespace at

A module-cellular/at/src/Cmd.cpp => module-cellular/at/src/Cmd.cpp +54 -0
@@ 0,0 1,54 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <Cmd.hpp>

namespace at
{
    Cmd::Cmd(std::string cmd, cmd::Modifier mod, std::chrono::milliseconds timeout) noexcept
        : cmd(std::move(cmd)), timeout(timeout), mod(mod)
    {}

    Cmd::Cmd(std::string cmd, std::chrono::milliseconds timeout) noexcept : Cmd(cmd, cmd::Modifier::None, timeout)
    {}

    Cmd::Cmd(const Cmd &p) noexcept
    {
        *this = operator=(p);
    }

    Cmd::Cmd(Cmd &&p) noexcept
    {
        *this = operator=(p);
    }

    auto Cmd::operator=(const Cmd &p) noexcept -> Cmd &
    {
        if (&p == this) {
            return *this;
        }
        this->cmd     = p.cmd;
        this->timeout = p.timeout;
        this->mod     = p.mod;
        this->body    = p.body;
        return *this;
    }

    auto Cmd::operator=(Cmd &&p) noexcept -> Cmd &
    {
        if (&p == this) {
            return *this;
        }
        this->cmd     = std::move(p.cmd);
        this->timeout = std::move(p.timeout);
        this->mod     = std::move(p.mod);
        this->body    = std::move(p.body);
        return *this;
    }

    Result &Cmd::parse(Result &that)
    {
        result = std::unique_ptr<Result>();
        return *result;
    }
} // namespace at

M module-cellular/test/CMakeLists.txt => module-cellular/test/CMakeLists.txt +9 -0
@@ 19,3 19,12 @@ add_catch2_executable(
        module-cellular
)

add_catch2_executable(
        NAME
        unittest_parse_CSCA
        SRCS
        unittest_parse_CSCA.cpp
        LIBS
        module-cellular
)


A module-cellular/test/mock/AtCommon_channel.hpp => module-cellular/test/mock/AtCommon_channel.hpp +99 -0
@@ 0,0 1,99 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "Modem/BaseChannel.hpp"

namespace at
{

    class ChannelMock : public BaseChannel
    {
      public:
        virtual Result ResultMock()
        {
            return Result();
        }

        auto cmd(const std::string &cmd,
                 std::chrono::milliseconds timeout = at::default_timeout,
                 size_t rxCount                    = 0) noexcept -> Result override
        {
            return ResultMock();
        }
        auto cmd(const at::AT &at) noexcept -> Result override
        {
            return ResultMock();
        }
        auto cmd(const at::Cmd &at) -> Result override
        {
            return ResultMock();
        }

        void cmd_init() override
        {}

        void cmd_send(std::string cmd) override
        {}

        void cmd_post() override
        {}

        std::vector<std::string> cmd_receive() override
        {
            return {};
        }
    };

    class FailingChannel : public ChannelMock
    {
        virtual Result ResultMock()
        {
            auto r = Result();
            r.code = Result::Code::ERROR;
            return r;
        }
    };

    /// provides CSCS bad response
    class CSCS_badChannel : public ChannelMock
    {
        virtual Result ResultMock()
        {
            auto r     = Result();
            r.code     = Result::Code::ERROR;
            r.response = {"LOL", "XD"};
            return r;
        }
    };

    /// standard bad CSCA values I get from modem (with result OK)
    class CSCA_emptyData : public ChannelMock
    {
        virtual Result ResultMock()
        {
            auto r     = Result();
            r.code     = Result::Code::OK;
            r.response = {"+CSCA: \"\","};
            return r;
        }
    };

    /// provides proper CSCS reponse
    class CSCS_successChannel : public ChannelMock
    {
      public:
        std::string smsCenterAddress = "\"0700\"";
        std::string smsTypeOfAddress = "124";

        auto ResultMock() -> Result override
        {
            auto r = Result();
            r.code = Result::Code::OK;
            // space below side to CSCS matters
            r.response = {"+CSCA: " + smsCenterAddress + "," + smsTypeOfAddress, "OK"};
            return r;
        }
    };
} // namespace at

A module-cellular/test/unittest_parse_CSCA.cpp => module-cellular/test/unittest_parse_CSCA.cpp +85 -0
@@ 0,0 1,85 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <string>
#define CATCH_CONFIG_MAIN

#include <catch2/catch.hpp>
#include <at/cmd/CSCA.hpp>
#include "mock/AtCommon_channel.hpp"
#include "PhoneNumber.hpp"
#include "Result.hpp"

TEST_CASE("CSCA parser test")
{
    SECTION("empty failed data")
    {
        at::cmd::CSCA cmd;
        at::Result base_result; // normally returned from cmux->exec(), TODO getter for dumb result ala exe
        auto &result = cmd.parse(base_result);
        REQUIRE(!result);
        REQUIRE(result.smsCenterAddress == "");
        REQUIRE(result.smsTypeOfAddress == "");
    }

    SECTION("failing channel")
    {
        at::cmd::CSCA cmd;
        at::CSCS_badChannel channel;
        auto base = channel.cmd(cmd);
        auto resp = cmd.parse(base);
        REQUIRE(!resp);
        REQUIRE(resp.code == at::Result::Code::ERROR);
    }

    SECTION("bad data")
    {
        at::cmd::CSCA cmd(at::cmd::Modifier::Get);
        at::CSCA_emptyData channel;
        auto base = channel.cmd(cmd);
        auto resp = cmd.parse(base);
        REQUIRE(!resp);
        REQUIRE(resp.code == at::Result::Code::PARSING_ERROR);
    }

    SECTION("proper data")
    {
        at::cmd::CSCA cmd;
        at::CSCS_successChannel channel;
        auto base = channel.cmd(cmd);
        auto resp = cmd.parse(base);
        REQUIRE(resp);
        REQUIRE(resp.smsCenterAddress == channel.smsCenterAddress);
        REQUIRE(resp.smsTypeOfAddress == channel.smsTypeOfAddress);
    }

    SECTION("failing data")
    {}
}

TEST_CASE("CSCA set data")
{
    SECTION("set proper data")
    {
        const std::string play_sms_center = "+48 790 998 250";
        const int example_type_of_address = 145;
        utils::PhoneNumber nr(play_sms_center);

        const std::string expected_result =
            "AT+CSCA=\"" + nr.getView().getE164() + "\"," + std::to_string(example_type_of_address);

        at::cmd::CSCA cmd;
        cmd.set(nr.getView(), example_type_of_address);
        SECTION("We fail - `NONE` modifier set")
        {
            REQUIRE(cmd.getCmd() != expected_result);
        }

        cmd.setModifier(at::cmd::Modifier::Set);

        SECTION("Success: proper modifier and data set")
        {
            REQUIRE(cmd.getCmd() == expected_result);
        }
    }
}

M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +1 -0
@@ 5,6 5,7 @@ set(SOURCES
    CellularCall.cpp
    CellularServiceAPI.cpp
    CellularUrcHandler.cpp
    checkSmsCenter.cpp
    ServiceCellular.cpp
    SignalStrength.cpp
    SimCard.cpp

M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +26 -15
@@ 82,6 82,7 @@
#include <string>
#include <utility>
#include <vector>
#include "checkSmsCenter.hpp"

const char *ServiceCellular::serviceName = "ServiceCellular";



@@ 775,7 776,7 @@ sys::MessagePointer ServiceCellular::DataReceivedHandler(sys::DataMessage *msgl,
                break;
            }
            auto respMsg      = std::make_shared<cellular::RawCommandResp>(true);
            auto ret          = channel->cmd(m->command.c_str(), m->timeout);
            auto ret          = channel->cmd(m->command, std::chrono::milliseconds(m->timeout));
            respMsg->response = ret.response;
            if (respMsg->response.size()) {
                for (auto const &el : respMsg->response) {


@@ 1383,17 1384,18 @@ bool ServiceCellular::sendSMS(SMSRecord record)
    constexpr uint32_t singleMessageLen = 30;
    bool result                         = false;
    auto channel                        = cmux->get(TS0710::Channel::Commands);
    auto receiver                       = record.number.getEntered();
    if (channel) {
        channel->cmd(at::AT::SET_SMS_TEXT_MODE_UCS2);
        channel->cmd(at::AT::SMS_UCSC2);
        // if text fit in single message send
        if (textLen < singleMessageLen) {

            std::string command      = std::string(at::factory(at::AT::CMGS));
            std::string body         = UCS2(UTF8(receiver)).str();
            std::string suffix       = "\"";
            std::string command_data = command + body + suffix;
            if (cmux->CheckATCommandPrompt(channel->SendCommandPrompt(
                    (std::string(at::factory(at::AT::CMGS)) + UCS2(UTF8(record.number.getEntered())).str() + "\"")
                        .c_str(),
                    1,
                    commandTimeout))) {
                    command_data.c_str(), 1, at::factory(at::AT::CMGS).getTimeout().count()))) {

                if (channel->cmd((UCS2(record.body).str() + "\032").c_str())) {
                    result = true;


@@ 1401,6 1403,8 @@ bool ServiceCellular::sendSMS(SMSRecord record)
                else {
                    result = false;
                }
                if (!result)
                    LOG_ERROR("Message to: %s send failure", receiver.c_str());
            }
        }
        // split text, and send concatenated messages


@@ 1426,21 1430,25 @@ bool ServiceCellular::sendSMS(SMSRecord record)
                }
                UTF8 messagePart = record.body.substr(i * singleMessageLen, partLength);

                std::string command(at::factory(at::AT::QCMGS) + UCS2(UTF8(record.number.getEntered())).str() +
                                    "\",120," + std::to_string(i + 1) + "," + std::to_string(messagePartsCount));
                std::string command(at::factory(at::AT::QCMGS) + UCS2(UTF8(receiver)).str() + "\",120," +
                                    std::to_string(i + 1) + "," + std::to_string(messagePartsCount));

                if (cmux->CheckATCommandPrompt(channel->SendCommandPrompt(command.c_str(), 1, commandTimeout))) {
                    // prompt sign received, send data ended by "Ctrl+Z"
                    if (channel->cmd((UCS2(messagePart).str() + "\032").c_str(), commandTimeout, 2)) {
                    if (channel->cmd(UCS2(messagePart).str() + "\032", std::chrono::milliseconds(commandTimeout), 2)) {
                        result = true;
                    }
                    else {
                        result = false;
                        if (!result)
                            LOG_ERROR("Message send failure");
                        break;
                    }
                }
                else {
                    result = false;
                    if (!result)
                        LOG_ERROR("Message send failure");
                    break;
                }
            }


@@ 1453,6 1461,9 @@ bool ServiceCellular::sendSMS(SMSRecord record)
    else {
        LOG_INFO("SMS sending failed.");
        record.type = SMSType::FAILED;
        if (checkSmsCenter(*channel)) {
            LOG_ERROR("SMS center check");
        }
    }
    DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<db::query::SMSUpdate>(record));



@@ 1470,7 1481,7 @@ bool ServiceCellular::receiveSMS(std::string messageNumber)
    channel->cmd(at::AT::SMS_UCSC2);

    auto cmd           = at::factory(at::AT::QCMGR);
    auto ret           = channel->cmd(cmd + messageNumber, cmd.timeout);
    auto ret           = channel->cmd(cmd + messageNumber, cmd.getTimeout());
    bool messageParsed = false;

    std::string messageRawBody;


@@ 1952,7 1963,7 @@ bool ServiceCellular::SetScanMode(std::string mode)
    if (channel) {
        auto command = at::factory(at::AT::SET_SCANMODE);

        auto resp = channel->cmd(command.cmd + mode + ",1", 300, 1);
        auto resp = channel->cmd(command.getCmd() + mode + ",1", command.getTimeout(), 1);
        if (resp.code == at::Result::Code::OK) {
            return true;
        }


@@ 1984,10 1995,10 @@ bool ServiceCellular::transmitDtmfTone(uint32_t digit)
    if (channel) {
        auto command           = at::factory(at::AT::QLDTMF);
        std::string dtmfString = "\"" + std::string(1, digit) + "\"";
        resp                   = channel->cmd(command.cmd + dtmfString);
        resp                   = channel->cmd(command.getCmd() + dtmfString);
        if (resp) {
            command = at::factory(at::AT::VTS);
            resp    = channel->cmd(command.cmd + dtmfString);
            resp    = channel->cmd(command.getCmd() + dtmfString);
        }
    }
    return resp.code == at::Result::Code::OK;


@@ 2000,7 2011,7 @@ void ServiceCellular::handle_CellularGetChannelMessage()
        LOG_DEBUG("Handle request for channel: %s", TS0710::name(getChannelMsg->dataChannel).c_str());
        std::shared_ptr<CellularGetChannelResponseMessage> channelResponsMessage =
            std::make_shared<CellularGetChannelResponseMessage>(cmux->get(getChannelMsg->dataChannel));
        LOG_DEBUG("chanel ptr: %p", channelResponsMessage->dataChannelPtr);
        LOG_DEBUG("channel ptr: %p", channelResponsMessage->dataChannelPtr);
        sys::Bus::SendUnicast(std::move(channelResponsMessage), req->sender, this);
        return sys::MessageNone{};
    });


@@ 2097,7 2108,7 @@ bool ServiceCellular::handleUSSDRequest(CellularUSSDMessage::RequestType request
        if (requestType == CellularUSSDMessage::RequestType::pullSesionRequest) {
            channel->cmd(at::AT::SMS_GSM);
            std::string command = at::factory(at::AT::CUSD_SEND) + request + ",15";
            auto result         = channel->cmd(command, commandTimeout, commandExpectedTokens);
            auto result = channel->cmd(command, std::chrono::milliseconds(commandTimeout), commandExpectedTokens);
            if (result.code == at::Result::Code::OK) {
                ussdState = ussd::State::pullRequestSent;
                setUSSDTimer();

A module-services/service-cellular/checkSmsCenter.cpp => module-services/service-cellular/checkSmsCenter.cpp +20 -0
@@ 0,0 1,20 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "checkSmsCenter.hpp"
#include "Modem/BaseChannel.hpp"

[[nodiscard]] bool checkSmsCenter(at::BaseChannel &channel)
{
    auto success        = true;
    auto smsCenterCmd   = at::cmd::CSCA(at::cmd::Modifier::Get);
    auto resp           = channel.cmd(smsCenterCmd);
    auto centerResponse = smsCenterCmd.parse(resp);
    if (!centerResponse) {
        success = false;
        if (centerResponse.code == at::Result::Code::PARSING_ERROR) {
            LOG_FATAL("possibly unrecoverable SMS center value error!");
        }
    }
    return success;
}

A module-services/service-cellular/checkSmsCenter.hpp => module-services/service-cellular/checkSmsCenter.hpp +11 -0
@@ 0,0 1,11 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

namespace at
{
    class BaseChannel;
}

[[nodiscard]] auto checkSmsCenter(at::BaseChannel &chanel) -> bool;

M module-services/service-fota/ServiceFota.cpp => module-services/service-fota/ServiceFota.cpp +1 -1
@@ 516,7 516,7 @@ namespace FotaService

    at::Result Service::sendAndLogError(const std::string &msg, uint32_t timeout) const
    {
        at::Result result = dataChannel->cmd(msg, timeout);
        at::Result result = dataChannel->cmd(msg, std::chrono::milliseconds(timeout));
        logIfError(result, msg);
        return result;
    }