~aleteoryx/muditaos

cee68d98eb76a5e757ff90398eca8ab6484a7341 — Jakub Pyszczak 5 years ago a0b7964
[EGD-5529] Added CLCC parser

New cmd mechanism implementation of CLCC parser.
Unit tests implementation for the parser.
Refactor of searching for active outgoing call
in service cellular.
M enabled_unittests => enabled_unittests +9 -0
@@ 77,6 77,15 @@ TESTS_LIST["catch2-cellular-URC"]="
    +Qiurc: TCP Context and connection message;
"
#---------
TESTS_LIST["catch2-cellular-parse-result"]="
    CSCA parser test;
    CSCA set data;
    QECCNUM parser;
    CLCC parser;
    CLCC set data;
    CLCC conversion methods;
"
#---------
TESTS_LIST["catch2-commands-queue-tests"]="
    DrawCommandsQueueTests;
"

M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +1 -0
@@ 41,6 41,7 @@ set(SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/at/response.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/CSCA.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/QECCNUM.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/CLCC.cpp
        )

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

M module-cellular/at/Cmd.hpp => module-cellular/at/Cmd.hpp +2 -0
@@ 33,6 33,8 @@ namespace at
      private:
        std::string cmd;                                     /// command head to run (AT, CLCC etc...)
        std::chrono::milliseconds timeout = default_timeout; /// timeout for this command
        void split(const std::string &str, Result &result) const;

      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

M module-cellular/at/Result.hpp => module-cellular/at/Result.hpp +1 -0
@@ 42,6 42,7 @@ namespace at
        std::variant<at::EquipmentErrorCode, at::NetworkErrorCode> errorCode = at::EquipmentErrorCode::NoInformation;

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

        virtual explicit operator bool() const
        {

A module-cellular/at/cmd/CLCC.hpp => module-cellular/at/cmd/CLCC.hpp +92 -0
@@ 0,0 1,92 @@
// 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 <PhoneNumber.hpp>

namespace ModemCall
{
    enum class CallState : uint8_t
    {
        Active = 0, // 0 active: call in progress (setup was successful)
        Held,       // 1 held: call on hold
        Dialing,    // 2 dialing (MO call): number dialed
        Alerting,   // 3 alerting (MO call): number dialed and the called party is alerted
        Incoming,   // 4 incoming (MT call): incoming call, ringtone played (AT RING notification)
        Waiting // 5 waiting (MT call): call waiting notification while another call is active (if call waiting feature
                // enabled)
    };

    enum class CallDir : uint8_t
    {
        MO = 0, // Mobile originated (MO) call
        MT = 1  // Mobile terminated (MT) call
    };

    enum class CallMode : uint8_t
    {
        Voice = 0,
        Data,
        FAX
    };
} // namespace ModemCall

namespace at
{
    namespace cmd
    {
        class CLCC;
    } // namespace cmd

    namespace result
    {
        /// please see documentation:
        /// QuectelEC2526EC21ATCommandsManualV13.1100970659
        /// page: 101 for more information
        struct CLCC : public Result
        {
          private:
            struct Data
            {
                const std::uint8_t idx;
                const ModemCall::CallDir dir;
                const ModemCall::CallState stateOfCall;
                const ModemCall::CallMode mode;
                const bool multiparty;
                const utils::PhoneNumber::View number;
                const std::string type;
                const std::string alpha;
                const std::size_t tokens;
            };

            std::vector<Data> data;
            friend cmd::CLCC;

          public:
            explicit CLCC(const Result &);
            [[nodiscard]] auto getData() const noexcept -> const std::vector<Data> &
            {
                return data;
            };
        };
    } // namespace result

    namespace cmd
    {
        class CLCC : public Cmd
        {
          protected:
            [[nodiscard]] static auto toBool(const std::string &text) -> bool;
            [[nodiscard]] static auto toUInt(const std::string &text) -> std::uint8_t;
            template <typename T>[[nodiscard]] static auto toEnum(const std::string &text) -> std::optional<T>;

          public:
            CLCC() noexcept;
            explicit CLCC(at::cmd::Modifier mod) noexcept;
            [[nodiscard]] auto parse(Result &base_result) -> result::CLCC & final;
        };
    } // namespace cmd
} // namespace at

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

#include <at/cmd/CLCC.hpp>
#include <memory>
#include <string>
#include <string_view>
#include <service-cellular/CellularCall.hpp>

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

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

        auto CLCC::toBool(const std::string &text) -> bool
        {
            int ret = -1;
            if (!utils::toNumeric(text, ret) || ret == -1) {
                throw std::runtime_error("String to bool conversion failed");
            }
            return ret == 1 ? true : false;
        }

        auto CLCC::toUInt(const std::string &text) -> std::uint8_t
        {
            auto value = std::stoul(text);
            if (value > std::numeric_limits<std::uint8_t>::max()) {
                throw std::runtime_error("Out of range value");
            }
            return static_cast<std::uint8_t>(value);
        }

        template <typename T> auto CLCC::toEnum(const std::string &text) -> std::optional<T>
        {
            static_assert(std::is_enum_v<T>);
            int ret = -1;
            if (!utils::toNumeric(text, ret) || ret == -1) {
                return std::nullopt;
            }

            return magic_enum::enum_cast<T>(ret);
        }

        result::CLCC &CLCC::parse(Result &base_result)
        {
            using namespace std::literals;
            auto &baseResult = Cmd::parse(base_result);
            auto p           = new result::CLCC(baseResult);
            result           = std::unique_ptr<result::CLCC>(p);

            auto parseErrorHandler = [&p]() -> result::CLCC & {
                LOG_ERROR("Parsing error - invalid parameter passed");
                p->code = result::CLCC::Code::PARSING_ERROR;
                return *p;
            };
            if (p && p->tokens.size() > 0) {
                for (const auto &tokens : p->tokens) {
                    auto numberOfTokens = tokens.size();
                    if (numberOfTokens != 7 && numberOfTokens != 8) {
                        LOG_ERROR("Can't parse - number of tokens %u is not valid",
                                  static_cast<unsigned int>(result->tokens.size()));
                        p->code = result::CLCC::Code::PARSING_ERROR;
                        return *p;
                    }

                    try {
                        const auto callDir   = toEnum<ModemCall::CallDir>(tokens[1]);
                        const auto callState = toEnum<ModemCall::CallState>(tokens[2]);
                        const auto callMode  = toEnum<ModemCall::CallMode>(tokens[3]);
                        if (!callDir.has_value() || !callState.has_value() || !callMode.has_value()) {
                            return parseErrorHandler();
                        }
                        p->data.push_back(result::CLCC::Data{toUInt(tokens[0]),
                                                             callDir.value(),
                                                             callState.value(),
                                                             callMode.value(),
                                                             toBool(tokens[4]),
                                                             utils::PhoneNumber(tokens[5]).getView(),
                                                             tokens[6],
                                                             (numberOfTokens == 8) ? tokens[7] : "",
                                                             numberOfTokens});
                    }
                    catch (...) {
                        return parseErrorHandler();
                    }
                }
            }
            return *p;
        }
    } // namespace cmd

    namespace result
    {
        CLCC::CLCC(const Result &result) : Result{result}
        {}

    } // namespace result
} // namespace at

M module-cellular/at/src/Cmd.cpp => module-cellular/at/src/Cmd.cpp +45 -2
@@ 1,7 1,8 @@
// 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>
#include "Cmd.hpp"
#include <algorithm>

namespace at
{


@@ 22,6 23,20 @@ namespace at
        *this = operator=(p);
    }

    void Cmd::split(const std::string &str, Result &result) const
    {
        constexpr char tokenDelimiter = ',';

        result.tokens.push_back(utils::split(str, tokenDelimiter));
        constexpr auto urcStringDelimiter = "\"";
        for (auto &arr : result.tokens) {
            for (auto &t : arr) {
                utils::findAndReplaceAll(t, urcStringDelimiter, "");
                t = utils::trim(t);
            }
        }
    }

    auto Cmd::operator=(const Cmd &p) noexcept -> Cmd &
    {
        if (&p == this) {


@@ 48,7 63,35 @@ namespace at

    Result &Cmd::parse(Result &that)
    {
        result = std::unique_ptr<Result>();
        result = std::make_unique<Result>(that);
        if (result->code != Result::Code::OK) {
            return *result;
        }
        else if (result->response.empty()) {
            LOG_ERROR("Can't parse - empty response");
            result->code = Result::Code::PARSING_ERROR;
            return *result;
        }
        else if (result->response.size() == 1) {
            if (result->response[0] == "OK") {
                LOG_INFO("OK response");
                result->code = Result::Code::OK;
                return *result;
            }
            LOG_ERROR("Can't parse - response not valid");
            result->code = Result::Code::ERROR;
            return *result;
        }
        const char headDelimiter = ':';
        const auto atResponse    = result->response;
        const auto lastPosition  = atResponse.end() - 1;
        for (auto it = atResponse.begin(); it != lastPosition; it++) {
            auto prefixIter = std::find(it->begin(), it->end(), headDelimiter);
            auto head       = std::string(it->begin(), prefixIter);
            auto body       = std::string(prefixIter == it->end() ? it->begin() : prefixIter + 1, it->end());
            split(body, *result);
        }

        return *result;
    }
} // namespace at

M module-cellular/test/CMakeLists.txt => module-cellular/test/CMakeLists.txt +0 -1
@@ 28,7 28,6 @@ add_catch2_executable(
        module-cellular
)


add_catch2_executable(
        NAME
        unittest_ATStream

M module-cellular/test/mock/AtCommon_channel.hpp => module-cellular/test/mock/AtCommon_channel.hpp +81 -0
@@ 111,4 111,85 @@ namespace at
            return r;
        }
    };

    /// provides proper CLCC response with one call on the list
    class CLCC_successChannel_oneCall : public ChannelMock
    {
      public:
        const std::string idx         = "1";
        const std::string dir         = "0";
        const std::string stateOfCall = "0";
        const std::string mode        = "0";
        const std::string multiparty  = "1";
        const std::string number      = "10086";
        const std::string type        = "129";

        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+CLCC: " + idx + "," + dir + "," + stateOfCall + "," + mode + "," + multiparty + "," +
                                   number + "," + type,
                               "OK"};
            return result;
        }
    };

    /// provides proper CLCC response with two calls on the list
    class CLCC_successChannel_twoCalls : public ChannelMock
    {
      public:
        const std::string idx1{"1"};
        const std::string dir1{"0"};
        const std::string stateOfCall1{"0"};
        const std::string mode1{"1"};
        const std::string multiparty1{"0"};
        const std::string number1{""};
        const std::string type1{"129"};

        const std::string idx2{"2"};
        const std::string dir2{"0"};
        const std::string stateOfCall2{"0"};
        const std::string mode2{"0"};
        const std::string multiparty2{"0"};
        const std::string number2{"10086"};
        const std::string type2{"129"};

        const std::string tokensLTEMode{"1,0,0,1,0,\"\",129"};
        const std::string tokensEstablishCall{"2,0,0,0,0,\"10086\",129"};

        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+CLCC: " + tokensLTEMode, "+CLCC: " + tokensEstablishCall, "OK"};
            return result;
        }
    };

    // Provides succesfull 'OK' response
    class OK_Channel : public ChannelMock
    {
      public:
        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"OK"};
            return result;
        }
    };

    // Provides bad 'OG' response
    class OG_Channel : public ChannelMock
    {
      public:
        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"OG"};
            return result;
        }
    };
} // namespace at

M module-cellular/test/unittest_parse_result.cpp => module-cellular/test/unittest_parse_result.cpp +189 -0
@@ 6,6 6,7 @@

#include <catch2/catch.hpp>

#include <at/cmd/CLCC.hpp>
#include <at/cmd/CSCA.hpp>
#include <at/cmd/QECCNUM.hpp>



@@ 13,6 14,26 @@
#include "PhoneNumber.hpp"
#include "Result.hpp"

namespace at::cmd
{
    struct CLCCStub : public CLCC
    {
        [[nodiscard]] static auto toBool(const std::string &text) -> bool
        {
            return CLCC::toBool(text);
        }
        [[nodiscard]] static auto toUInt(const std::string &text) -> std::uint8_t
        {
            return CLCC::toUInt(text);
        }

        template <typename T>[[nodiscard]] static auto toEnum(const std::string &text) -> std::optional<T>
        {
            return CLCC::toEnum<T>(text);
        }
    };
} // namespace at::cmd

TEST_CASE("CSCA parser test")
{
    SECTION("empty failed data")


@@ 149,3 170,171 @@ TEST_CASE("QECCNUM parser")
        REQUIRE(cmdAddSim.getCmd() == "AT+QECCNUM=1,0,\"600800900\",\"112\"");
    }
}

TEST_CASE("CLCC parser")
{
    using namespace std::string_literals;
    SECTION("Empty data")
    {
        at::cmd::CLCC cmd;
        at::Result result;
        auto response = cmd.parse(result);
        REQUIRE(!response);
    }
    SECTION("Failing channel")
    {
        at::cmd::CLCC cmd;
        at::FailingChannel channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parse(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::ERROR);
    }
    SECTION("Success - one call")
    {
        at::cmd::CLCC cmd;
        at::CLCC_successChannel_oneCall channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parse(base);
        REQUIRE(response);
        auto [idx, dir, stateOfCall, mode, multiparty, number, type, alpha, tokens] = response.getData()[0];
        REQUIRE(idx == 1);
        REQUIRE(dir == ModemCall::CallDir::MO);
        REQUIRE(stateOfCall == ModemCall::CallState::Active);
        REQUIRE(mode == ModemCall::CallMode::Voice);
        REQUIRE(multiparty == true);
        REQUIRE(number == utils::PhoneNumber(channel.number).getView());
        REQUIRE(type == "129"s);
        REQUIRE(alpha == "");
    }

    SECTION("Success - two calls")
    {
        at::cmd::CLCC cmd;
        at::CLCC_successChannel_twoCalls channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parse(base);
        REQUIRE(response);
        SECTION("First entry")
        {
            auto [idx, dir, stateOfCall, mode, multiparty, number, type, alpha, tokens] = response.getData()[0];
            REQUIRE(idx == 1);
            REQUIRE(dir == ModemCall::CallDir::MO);
            REQUIRE(stateOfCall == ModemCall::CallState::Active);
            REQUIRE(mode == ModemCall::CallMode::Data);
            REQUIRE(multiparty == false);
            REQUIRE(number == utils::PhoneNumber(channel.number1).getView());
            REQUIRE(type == "129"s);
            REQUIRE(alpha == "");
        }

        SECTION("Second entry")
        {
            auto [idx, dir, stateOfCall, mode, multiparty, number, type, alpha, tokens] = response.getData()[1];
            REQUIRE(idx == 2);
            REQUIRE(dir == ModemCall::CallDir::MO);
            REQUIRE(stateOfCall == ModemCall::CallState::Active);
            REQUIRE(mode == ModemCall::CallMode::Voice);
            REQUIRE(multiparty == false);
            REQUIRE(number == utils::PhoneNumber(channel.number2).getView());
            REQUIRE(type == "129"s);
            REQUIRE(alpha == "");
        }
    }

    SECTION("Failed - bad channel result")
    {
        at::cmd::CLCC 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("Success - only OK data")
    {
        at::cmd::CLCC cmd;
        at::OK_Channel channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parse(base);
        REQUIRE(response);
    }

    SECTION("Failed - only OG data")
    {
        at::cmd::CLCC cmd;
        at::OG_Channel channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parse(base);
        REQUIRE(response.code == at::Result::Code::ERROR);
    }
}

TEST_CASE("CLCC set data")
{
    using namespace std::literals;
    at::cmd::CLCC cmd;
    SECTION("None modifier set")
    {
        constexpr auto expectedResult = "AT+CLCC"sv;
        REQUIRE(cmd.getCmd() == expectedResult);
    }

    SECTION("Get modifier set")
    {
        constexpr auto expectedResult = "AT+CLCC?"sv;
        cmd.setModifier(at::cmd::Modifier::Get);
        REQUIRE(cmd.getCmd() == expectedResult);
    }
}

TEST_CASE("CLCC conversion methods")
{
    SECTION("String to bool")
    {
        SECTION("Proper data - true")
        {
            REQUIRE(at::cmd::CLCCStub::toBool("1") == true);
        }
        SECTION("Proper data - true")
        {
            REQUIRE(at::cmd::CLCCStub::toBool("0") == false);
        }
        SECTION("Invaild data")
        {
            REQUIRE_THROWS(at::cmd::CLCCStub::toBool("-1") == false);
        }
    }
    SECTION("String to uint32")
    {
        REQUIRE(at::cmd::CLCCStub::toUInt("1") == 1);
        REQUIRE(at::cmd::CLCCStub::toUInt("32") == 32);
        REQUIRE(at::cmd::CLCCStub::toUInt("255") == std::numeric_limits<std::uint8_t>::max());
        REQUIRE_THROWS(at::cmd::CLCCStub::toUInt("256"));
    }
    SECTION("String to enum")
    {
        SECTION("Call dir")
        {
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallDir>("-1") == std::nullopt);
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallDir>("0").value() == ModemCall::CallDir::MO);
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallDir>("1").value() == ModemCall::CallDir::MT);
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallDir>("2") == std::nullopt);
        }
        SECTION("Call state")
        {
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallState>("-1") == std::nullopt);
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallState>("0").value() == ModemCall::CallState::Active);
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallState>("5").value() == ModemCall::CallState::Waiting);
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallState>("6") == std::nullopt);
        }
        SECTION("Call mode")
        {
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallMode>("-1") == std::nullopt);
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallMode>("0").value() == ModemCall::CallMode::Voice);
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallMode>("2").value() == ModemCall::CallMode::FAX);
            REQUIRE(at::cmd::CLCCStub::toEnum<ModemCall::CallMode>("3") == std::nullopt);
        }
    }
}

M module-db/Interface/CalllogRecord.cpp => module-db/Interface/CalllogRecord.cpp +1 -4
@@ 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

#include "CalllogRecord.hpp"


@@ 45,9 45,6 @@ CalllogRecordInterface::CalllogRecordInterface(CalllogDB *calllogDb, ContactsDB 
    : calllogDB(calllogDb), contactsDB(contactsDb)
{}

CalllogRecordInterface::~CalllogRecordInterface()
{}

bool CalllogRecordInterface::Add(const CalllogRecord &rec)
{
    ContactRecordInterface contactInterface(contactsDB);

M module-db/Interface/CalllogRecord.hpp => module-db/Interface/CalllogRecord.hpp +1 -3
@@ 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


@@ 29,7 29,6 @@ struct CalllogRecord : public Record
    friend std::ostream &operator<<(std::ostream &out, const CalllogRecord &point);

    CalllogRecord()  = default;
    ~CalllogRecord() = default;
    CalllogRecord(const CalllogTableRow &tableRow);

    uint32_t getContactId() const;


@@ 45,7 44,6 @@ class CalllogRecordInterface : public RecordInterface<CalllogRecord, CalllogReco
{
  public:
    CalllogRecordInterface(CalllogDB *CalllogDb, ContactsDB *contactsDb);
    virtual ~CalllogRecordInterface();

    bool Add(const CalllogRecord &rec) override final;
    bool RemoveByID(uint32_t id) override final;

M module-services/service-cellular/CellularCall.cpp => module-services/service-cellular/CellularCall.cpp +0 -83
@@ 16,89 16,6 @@
#include <string>
#include <vector>

namespace ModemCall
{
    ModemCall::ModemCall(const std::string str)
    {
        const std::string prefix = "+CLCC: ";
        std::string callEntry    = str;

        // Check for a valid prefix
        if (callEntry.rfind(prefix, 0) != 0) {

            throw std::runtime_error("No valid prefix");
        }
        else {
            // remove the prefix
            callEntry.erase(0, prefix.length());
        }

        std::vector<std::string> tokens = utils::split(callEntry, ",");

        auto numberOfTokens = tokens.size();
        if (numberOfTokens != 7 && numberOfTokens != 8) {
            throw std::runtime_error("Wrong number of tokens" + std::to_string(numberOfTokens));
        }

        idx           = std::stoul(tokens[0]);
        auto conv_val = std::stoul(tokens[1]);
        auto tmp_dir  = magic_enum::enum_cast<CallDir>(conv_val);
        if (tmp_dir.has_value()) {
            dir = tmp_dir.value();
        }
        else {
            throw std::runtime_error("dir value out of range CallDir enum - " + tokens[1]);
        }

        conv_val       = std::stoul(tokens[2]);
        auto tmp_state = magic_enum::enum_cast<CallState>(conv_val);
        if (tmp_state.has_value()) {
            state = tmp_state.value();
        }
        else {
            throw std::runtime_error("state value out of range CallState enum - " + tokens[2]);
        }

        conv_val      = std::stoul(tokens[3]);
        auto tmp_mode = magic_enum::enum_cast<CallMode>(conv_val);
        if (tmp_mode.has_value()) {
            mode = tmp_mode.value();
        }
        else {
            throw std::runtime_error("mode value out of range CallMode enum - " + tokens[3]);
        }

        isConferenceCall = static_cast<bool>(std::stoul(tokens[4]));
        phoneNumber      = tokens[5];

        try {
            conv_val = std::stoul(tokens[6]);
        }
        catch (const std::logic_error &) {
            conv_val = 0;
        }
        type = conv_val;

        if (numberOfTokens == 8) {
            phoneBookName = tokens[7];
        }
    }

    std::ostream &operator<<(std::ostream &out, const ModemCall &call)
    {
        out << " <idx> " << call.idx << " <dir> " << static_cast<uint32_t>(call.dir) << " <stat> "
            << static_cast<uint32_t>(call.state) << " <mode> " << static_cast<uint32_t>(call.mode) << " <mpty> "
            << static_cast<uint32_t>(call.isConferenceCall) << " <number> " << call.phoneNumber << " <type> "
            << static_cast<uint32_t>(call.type);

        if (!call.phoneBookName.empty()) {
            out << " <alpha> " << call.phoneBookName;
        }

        return out;
    }
} // namespace ModemCall

namespace CellularCall
{
    bool CellularCall::startCall(const utils::PhoneNumber::View &number, const CallType type)

M module-services/service-cellular/CellularUrcHandler.hpp => module-services/service-cellular/CellularUrcHandler.hpp +2 -2
@@ 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


@@ 24,7 24,7 @@
class CellularUrcHandler : public at::urc::UrcHandler
{
  public:
    CellularUrcHandler(ServiceCellular &cellularService) : cellularService(cellularService)
    explicit CellularUrcHandler(ServiceCellular &cellularService) : cellularService(cellularService)
    {}

    void Handle(at::urc::Clip &urc) final;

M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +17 -28
@@ 36,6 36,7 @@
#include <Tables/CalllogTable.hpp>
#include <Tables/Record.hpp>
#include <Utils.hpp>
#include <at/cmd/CLCC.hpp>
#include <at/UrcClip.hpp>
#include <at/UrcCmti.hpp>
#include <at/UrcCreg.hpp>


@@ 225,9 226,8 @@ static bool isSettingsAutomaticTimeSyncEnabled()
void ServiceCellular::CallStateTimerHandler()
{
    LOG_DEBUG("CallStateTimerHandler");
    std::shared_ptr<CellularRequestMessage> msg =
        std::make_shared<CellularRequestMessage>(MessageType::CellularListCurrentCalls);
    bus.sendUnicast(msg, ServiceCellular::serviceName);
    auto msg = std::make_shared<CellularRequestMessage>(MessageType::CellularListCurrentCalls);
    bus.sendUnicast(std::move(msg), ServiceCellular::serviceName);
}

sys::ReturnCodes ServiceCellular::InitHandler()


@@ 822,33 822,22 @@ sys::MessagePointer ServiceCellular::DataReceivedHandler(sys::DataMessage *msgl,
        break;
    }
    case MessageType::CellularListCurrentCalls: {
        auto ret  = cmux->get(TS0710::Channel::Commands)->cmd(at::AT::CLCC);
        auto size = ret.response.size();
        if (ret && size > 1) {
            bool retVal = true;
            // sometimes there is additional active data connection, sometimes not
            auto callEntry = ret.response[size == 2 ? 0 : 1];

            try {
                ModemCall::ModemCall call(callEntry);
                LOG_DEBUG("%s", utils::to_string(call).c_str());
                // If call changed to "Active" state stop callStateTimer(used for polling for call state)
                if (call.state == ModemCall::CallState::Active) {
                    auto msg =
                        std::make_shared<CellularNotificationMessage>(CellularNotificationMessage::Type::CallActive);
                    bus.sendMulticast(msg, sys::BusChannel::ServiceCellularNotifications);
                    callStateTimer->stop();
                }
            }
            catch (const std::exception &e) {
                LOG_ERROR("exception \"%s\" was thrown", e.what());
                retVal = false;
        at::cmd::CLCC cmd;
        auto base = cmux->get(TS0710::Channel::Commands)->cmd(cmd);
        if (auto response = cmd.parse(base); response) {
            const auto &data = response.getData();
            auto it          = std::find_if(std::begin(data), std::end(data), [&](const auto &entry) {
                return entry.stateOfCall == ModemCall::CallState::Active && entry.mode == ModemCall::CallMode::Voice;
            });
            if (it != std::end(data)) {
                auto msg = std::make_shared<CellularNotificationMessage>(CellularNotificationMessage::Type::CallActive);
                bus.sendMulticast(std::move(msg), sys::BusChannel::ServiceCellularNotifications);
                callStateTimer->stop();
                responseMsg = std::make_shared<CellularResponseMessage>(true);
                break;
            }
            responseMsg = std::make_shared<CellularResponseMessage>(retVal);
        }
        else {
            responseMsg = std::make_shared<CellularResponseMessage>(false);
        }
        responseMsg = std::make_shared<CellularResponseMessage>(false);
    } break;

    case MessageType::CellularHangupCall: {

M module-services/service-cellular/service-cellular/CellularCall.hpp => module-services/service-cellular/service-cellular/CellularCall.hpp +0 -57
@@ 15,61 15,6 @@
#include <string>
#include <sys/types.h>

namespace ModemCall
{
    enum class CallState : uint8_t
    {
        Active = 0, // 0 active: call in progress (setup was successful)
        Held,       // 1 held: call on hold
        Dialing,    // 2 dialing (MO call): number dialed
        Alerting,   // 3 alerting (MO call): number dialed and the called party is alerted
        Incoming,   // 4 incoming (MT call): incoming call, ringtone played (AT RING notification)
        Waiting // 5 waiting (MT call): call waiting notification while another call is active (if call waiting feature
                // enabled)
    };

    enum class CallDir : uint8_t
    {
        MO = 0, // Mobile originated (MO) call
        MT = 1, // Mobile terminated (MT) call
    };

    enum class CallMode : uint8_t
    {
        Voice = 0,
        Data  = 1,
        FAX   = 2,
    };

    /// Usually contains one of defined values
    /// More details in 3GPP TS 24.008 subclause 10.5.4.7
    enum class CallType : uint8_t
    {
        UknownType      = 129,
        InternationType = 145, // contains the "+" character
        NationalType    = 161,
    };

    struct ModemCall
    {
        int8_t idx;
        CallDir dir;
        CallState state;
        CallMode mode;
        bool isConferenceCall;
        std::string phoneNumber;
        uint8_t type;              /// Usually contains on of values defined in CallType
        std::string phoneBookName; /// This field is defined in the AT+CLCC command response but our modem is
                                   /// not returning it.

        ModemCall()  = delete;
        ~ModemCall() = default;
        ModemCall(const std::string str);

        friend std::ostream &operator<<(std::ostream &out, const ModemCall &call);
    };
} // namespace ModemCall

namespace CellularCall
{
    enum class Forced : bool


@@ 118,8 63,6 @@ namespace CellularCall
            this->call.contactId   = 0;
        }

        ~CellularCall() = default;

        void setStartCallAction(const std::function<CalllogRecord(const CalllogRecord &rec)> callAction)
        {
            startCallAction = callAction;

M module-services/service-cellular/service-cellular/CellularMessage.hpp => module-services/service-cellular/service-cellular/CellularMessage.hpp +0 -3
@@ 28,7 28,6 @@ class CellularMessage : public sys::DataMessage
{
  public:
    CellularMessage(MessageType messageType) : sys::DataMessage(messageType){};
    virtual ~CellularMessage(){};
};

class CellularCallMessage : public CellularMessage


@@ 257,8 256,6 @@ class CellularRequestMessage : public CellularMessage
  public:
    CellularRequestMessage(MessageType messageType, std::string data = "") : CellularMessage(messageType), data(data)
    {}
    ~CellularRequestMessage()
    {}

    std::string data;
};