~aleteoryx/muditaos

a5f957823daa836b22898fdc1527213f8f72f5d1 — breichel 5 years ago e7b9bcf
[EGD-4628] Add APN settings and URC handler for PDP context

Classes supporting APN settings on modem and telephone
configuration (synchronization) have been constructed.
Possibility of asynchronous execution
of individual actions via ServiceCellularAPI.
Added message with information about deactivation of the
PDP context (URC) that the
application / service can listen on.
34 files changed, 1268 insertions(+), 23 deletions(-)

M module-cellular/CMakeLists.txt
M module-cellular/at/Commands.hpp
M module-cellular/at/UrcClip.hpp
M module-cellular/at/UrcCmti.hpp
M module-cellular/at/UrcCpin.hpp
M module-cellular/at/UrcCreg.hpp
M module-cellular/at/UrcCtze.hpp
M module-cellular/at/UrcCusd.hpp
M module-cellular/at/UrcHandler.hpp
M module-cellular/at/UrcPoweredDown.hpp
M module-cellular/at/UrcQind.hpp
A module-cellular/at/UrcQiurc.hpp
M module-cellular/at/UrcResponse.hpp
M module-cellular/at/response.cpp
M module-cellular/at/response.hpp
M module-cellular/at/src/UrcFactory.cpp
A module-cellular/at/src/UrcQiurc.cpp
M module-cellular/test/unittest_URC.cpp
M module-services/service-cellular/CMakeLists.txt
M module-services/service-cellular/CellularServiceAPI.cpp
M module-services/service-cellular/CellularUrcHandler.cpp
M module-services/service-cellular/CellularUrcHandler.hpp
A module-services/service-cellular/PacketData.cpp
A module-services/service-cellular/PacketData.hpp
M module-services/service-cellular/ServiceCellular.cpp
M module-services/service-cellular/service-cellular/CellularServiceAPI.hpp
A module-services/service-cellular/service-cellular/PacketDataCellularMessage.hpp
A module-services/service-cellular/service-cellular/PacketDataTypes.hpp
M module-services/service-cellular/service-cellular/ServiceCellular.hpp
M module-services/service-cellular/service-cellular/State.hpp
M module-services/service-cellular/tests/CMakeLists.txt
A module-services/service-cellular/tests/unittest_datatransfer.cpp
M module-services/service-fota/FotaUrcHandler.hpp
M source/MessageType.hpp
M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +1 -0
@@ 31,6 31,7 @@ set(SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcCmti.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcClip.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcCpin.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcQiurc.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcPoweredDown.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcResponse.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcFactory.cpp

M module-cellular/at/Commands.hpp => module-cellular/at/Commands.hpp +8 -2
@@ 151,7 151,10 @@ namespace at
        COLP_GET,
        COLP_ENABLE,
        COLP_DISABLE,
        CSSN, /// Supplementary Services - Supplementary Service Notifications
        CSSN,   /// Supplementary Services - Supplementary Service Notifications
        QICSGP, /// Configure Parameters of a TCP/IP Context
        QIACT,  /// Activate a PDP Context
        QIDEACT /// Deactivate a PDP Context
    };

    // below timeouts are defined in Quectel_EC25&EC21_AT_Commands_Manual_V1.3.pdf


@@ 248,7 251,10 @@ namespace at
            {AT::COLP_GET, {"AT+COLP?", default_long_doc_timeout}},
            {AT::COLP_ENABLE, {"AT+COLP=1", default_long_doc_timeout}},
            {AT::COLP_DISABLE, {"AT+COLP=0", default_long_doc_timeout}},
            {AT::CSSN, {"AT+CSSN=\"", default_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}}};

        if (fact.count(at)) {
            return fact.at(at);

M module-cellular/at/UrcClip.hpp => module-cellular/at/UrcClip.hpp +1 -1
@@ 31,7 31,7 @@ namespace at::urc
        };

        static constexpr std::string_view head = "+CLIP";
        static bool isURC(const std::string uHead)
        static bool isURC(const std::string &uHead)
        {
            return uHead.find(Clip::head) != std::string::npos;
        }

M module-cellular/at/UrcCmti.hpp => module-cellular/at/UrcCmti.hpp +1 -1
@@ 17,7 17,7 @@ namespace at::urc

      public:
        static constexpr std::string_view head = "+CMTI";
        static auto isURC(const std::string uHead) -> bool
        static auto isURC(const std::string &uHead) -> bool
        {
            return uHead.find(Cmti::head) != std::string::npos;
        }

M module-cellular/at/UrcCpin.hpp => module-cellular/at/UrcCpin.hpp +1 -1
@@ 43,7 43,7 @@ namespace at::urc

        static constexpr auto head = "+CPIN";

        static bool isURC(const std::string uHead)
        static bool isURC(const std::string &uHead)
        {
            return uHead.find(Cpin::head) != std::string::npos;
        }

M module-cellular/at/UrcCreg.hpp => module-cellular/at/UrcCreg.hpp +1 -1
@@ 24,7 24,7 @@ namespace at::urc

      public:
        static constexpr std::string_view head = "+CREG";
        static bool isURC(const std::string uHead)
        static bool isURC(const std::string &uHead)
        {
            return uHead.find(Creg::head) != std::string::npos;
        }

M module-cellular/at/UrcCtze.hpp => module-cellular/at/UrcCtze.hpp +1 -1
@@ 20,7 20,7 @@ namespace at::urc

      public:
        static constexpr std::string_view head = "+CTZE";
        static auto isURC(const std::string uHead) -> bool
        static auto isURC(const std::string &uHead) -> bool
        {
            return uHead.find(Ctze::head) != std::string::npos;
        }

M module-cellular/at/UrcCusd.hpp => module-cellular/at/UrcCusd.hpp +1 -1
@@ 32,7 32,7 @@ namespace at::urc
        };
        Cusd(const std::string &urcBody, const std::string &urcHead = std::string());
        static constexpr std::string_view head = "+CUSD";
        static auto isURC(const std::string uHead) -> bool
        static auto isURC(const std::string &uHead) -> bool
        {
            return uHead.find(Cusd::head) != std::string::npos;
        }

M module-cellular/at/UrcHandler.hpp => module-cellular/at/UrcHandler.hpp +2 -0
@@ 12,6 12,7 @@ namespace at::urc
    class Ctze;
    class Qind;
    class Cpin;
    class Qiurc;
    class PoweredDown;
    class UrcResponse;



@@ 25,6 26,7 @@ namespace at::urc
        virtual void Handle(Ctze &urc)        = 0;
        virtual void Handle(Qind &urc)        = 0;
        virtual void Handle(Cpin &urc)        = 0;
        virtual void Handle(Qiurc &urc)       = 0;
        virtual void Handle(PoweredDown &urc) = 0;
        virtual void Handle(UrcResponse &urc) = 0;
    };

M module-cellular/at/UrcPoweredDown.hpp => module-cellular/at/UrcPoweredDown.hpp +1 -1
@@ 12,7 12,7 @@ namespace at::urc
      public:
        static constexpr std::string_view head_immediate = "POWERED DOWN";
        static constexpr std::string_view head_normal    = "NORMAL POWER DOWN";
        static auto isURC(const std::string uHead) -> bool
        static auto isURC(const std::string &uHead) -> bool
        {
            auto isImmediatePowerDown = uHead.find(PoweredDown::head_immediate) != std::string::npos;
            auto isNormalPowerDown    = uHead.find(PoweredDown::head_normal) != std::string::npos;

M module-cellular/at/UrcQind.hpp => module-cellular/at/UrcQind.hpp +1 -1
@@ 48,7 48,7 @@ namespace at::urc
        };

        static constexpr std::string_view head = "+QIND";
        static auto isURC(const std::string uHead) -> bool
        static auto isURC(const std::string &uHead) -> bool
        {
            return uHead.find(Qind::head) != std::string::npos;
        }

A module-cellular/at/UrcQiurc.hpp => module-cellular/at/UrcQiurc.hpp +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

#pragma once

#include "Urc.hpp"

namespace at::urc
{

    /// +QIURC: <action>,[p1], [p2]  - multipurpose URC information (in general for TCP connection)
    class Qiurc : public Urc
    {
        /**
         * In general could be at last one param in +QIURC: "incoming full"
         * to such with CR LF messages contain data +QIURC:
         * "recv",<connectID>,<currentrecvlength>,<remoteIP>,<remote_port><CR><LF><data>
         */
        enum Tokens
        {
            Type       = 0,
            FirstParam = 1
        };

      public:
        enum class QIUrcMessages
        {
            DeactivateContext = 1
        };
        static constexpr auto qiurcPdpdeact      = "pdpdeact"; ///<  +QIURC:"pdpdeact",<contextID>
        static constexpr auto qiurcPdpdeactCount = 2;

        static constexpr auto head = "+QIURC";

        static bool isURC(const std::string &uHead)
        {
            return uHead.find(Qiurc::head) != std::string::npos;
        }

        using Urc::Urc;

        [[nodiscard]] auto isValid() const noexcept -> bool override;

        [[nodiscard]] auto getType() const noexcept -> std::optional<QIUrcMessages>;

        [[nodiscard]] auto getFirstParam() const noexcept -> std::optional<std::string>;

        void Handle(UrcHandler &h) final
        {
            h.Handle(*this);
        }
    };

} // namespace at::urc

M module-cellular/at/UrcResponse.hpp => module-cellular/at/UrcResponse.hpp +1 -1
@@ 23,7 23,7 @@ namespace at::urc
            NoAnswer
        };

        static auto isURC(const std::string uHead) -> std::optional<URCResponseType>
        static auto isURC(const std::string &uHead) -> std::optional<URCResponseType>
        {
            for (auto &resp : urcResponses) {
                if (uHead.find(resp.second) != std::string::npos) {

M module-cellular/at/response.cpp => module-cellular/at/response.cpp +19 -4
@@ 11,7 11,6 @@ namespace at
{
    namespace response
    {
        constexpr auto StringDelimiter = "\"";

        std::optional<std::string> getResponseLineATCommand(const at::Result &resp, std::string_view head)
        {


@@ 36,6 35,21 @@ namespace at
            }
            return std::nullopt;
        }
        std::optional<ResponseTokens> getTokensForATResults(const at::Result &resp, std::string_view head)
        {
            if (resp.code != at::Result::Code::OK)
                return std::nullopt;

            std::vector<std::vector<std::string>> parts;
            for (auto el : resp.response) {
                if (el.compare(0, head.length(), head) == 0) {
                    auto body = el.substr(head.length());
                    parts.push_back(utils::split(body, ","));
                }
            }

            return parts;
        }

        bool parseCOPS(const at::Result &resp, std::vector<cops::Operator> &ret)
        {


@@ 104,9 118,10 @@ namespace at
            const std::string_view AT_QPINC_SC = "+QPINC:";
            if (auto tokens = getTokensForATCommand(resp, AT_QPINC_SC); tokens) {
                constexpr int QPINC_TokensCount = 3;
                if ((*tokens).size() == QPINC_TokensCount) {
                    utils::toNumeric((*tokens)[1], ret.PinCounter);
                    utils::toNumeric((*tokens)[2], ret.PukCounter);
                auto pinc_tokens                = (*tokens);
                if (pinc_tokens.size() == QPINC_TokensCount) {
                    utils::toNumeric(pinc_tokens[1], ret.PinCounter);
                    utils::toNumeric(pinc_tokens[2], ret.PukCounter);
                    return true;
                }
            }

M module-cellular/at/response.hpp => module-cellular/at/response.hpp +26 -1
@@ 15,6 15,7 @@ namespace at
{
    namespace response
    {
        constexpr auto StringDelimiter = "\"";
        namespace qpinc
        {
            /// Structure that holds parsed information from AT+QPINC command


@@ 67,9 68,33 @@ namespace at
        } // namespace cops

        bool parseCOPS(const at::Result &resp, std::vector<cops::Operator> &ret);

        using ResponseTokens = std::vector<std::vector<std::string>>;
        std::vector<std::string> tokenize(std::string &response, std::string separator = ",");

        /**
         * For AT one line (+XYZ) response like:
         * +CPIN READY
         * OK
         */
        std::optional<std::vector<std::string>> getTokensForATCommand(const at::Result &resp, std::string_view head);

        /**
         * For AT multiline response like (last OK)
         * +QIACT:1,<context_state>,<context_type>[,<IP_address>]
         * [.....
         * +QIACT:16,<context_state>,<context_type>[,<IP_address>]]
         * OK
         *
         * response from function like QPING (not mention in DOC as URC), looks like (first OK)
         * OK
         * +QPING: 0,"61.135.169.125",32,192,255
         * +QPING: 0,"61.135.169.125",32,240,255
         * ...
         * +QPING: 0,4,4,0,192,479,287
         *
         * Warning: should not be used for URC !
         */
        std::optional<ResponseTokens> getTokensForATResults(const at::Result &resp, std::string_view head);
        bool parseCSQ(std::string response, std::string &result);
        bool parseCSQ(std::string cellularResponse, uint32_t &result);
        bool parseCREG(std::string &response, uint32_t &result);

M module-cellular/at/src/UrcFactory.cpp => module-cellular/at/src/UrcFactory.cpp +4 -0
@@ 12,6 12,7 @@
#include <UrcPoweredDown.hpp>
#include <UrcResponse.hpp>
#include <UrcCpin.hpp>
#include <UrcQiurc.hpp>
using namespace at::urc;

std::unique_ptr<Urc> UrcFactory::Create(const std::string &urcMessage)


@@ 51,6 52,9 @@ std::unique_ptr<Urc> UrcFactory::Create(const std::string &urcMessage)
    else if (auto type = UrcResponse::isURC(head)) {
        return std::make_unique<UrcResponse>(type.value());
    }
    else if (Qiurc::isURC(head)) {
        return std::make_unique<Qiurc>(body);
    }

    return std::make_unique<Urc>(body, head);
}

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

#include "UrcQiurc.hpp"

using namespace at::urc;

auto Qiurc::isValid() const noexcept -> bool
{
    return tokens.size() == qiurcPdpdeactCount; /// only support one message type
}

auto Qiurc::getType() const noexcept -> std::optional<QIUrcMessages>
{
    if (!isValid()) {
        return std::nullopt;
    }

    if (tokens[Tokens::Type] == qiurcPdpdeact) {
        return QIUrcMessages::DeactivateContext;
    }
    return std::nullopt;
}

auto Qiurc::getFirstParam() const noexcept -> std::optional<std::string>
{
    if (getType()) {
        if (isValid() && (*getType() == QIUrcMessages::DeactivateContext)) {
            return tokens[Tokens::FirstParam];
        }
    }
    return std::nullopt;
}

M module-cellular/test/unittest_URC.cpp => module-cellular/test/unittest_URC.cpp +33 -0
@@ 17,6 17,7 @@
#include "UrcCmti.hpp"
#include "UrcClip.hpp"
#include "UrcCpin.hpp"
#include "UrcQiurc.hpp"
#include "UrcPoweredDown.hpp"
#include "UrcResponse.hpp"
#include "UrcFactory.hpp"


@@ 808,3 809,35 @@ TEST_CASE("+Qind: SMS DONE")
        REQUIRE(qind->isSmsDone());
    }
}

TEST_CASE("+Qiurc: TCP Context and connection message")
{
    SECTION("PDP Context deactivate - normal message")
    {
        /// +QIURC:"pdpdeact",<contextID>

        auto urc   = at::urc::UrcFactory::Create("+QIURC: \"pdpdeact\",1");
        auto qiurc = getURC<at::urc::Qiurc>(urc);

        REQUIRE(qiurc);
        REQUIRE(qiurc->getType());
        REQUIRE(qiurc->isValid());
        REQUIRE(*qiurc->getType() == at::urc::Qiurc::QIUrcMessages::DeactivateContext);
        REQUIRE(*qiurc->getFirstParam() == "1");
    }

    SECTION("PDP Context deactivate - corrupted, but OK format")
    {
        auto urc   = at::urc::UrcFactory::Create("+QIURC:\"pdpdeactwww\",1");
        auto qiurc = getURC<at::urc::Qiurc>(urc);
        REQUIRE(qiurc);
        REQUIRE(qiurc->getType() == std::nullopt);
    }

    SECTION("PDP Context deactivate - wrong param count (in case simple implementation)")
    {
        auto urc   = at::urc::UrcFactory::Create("+QIURC:\"pdpdeactwww\",1,3");
        auto qiurc = getURC<at::urc::Qiurc>(urc);
        REQUIRE(qiurc->getType() == std::nullopt);
    }
}

M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +2 -1
@@ 1,4 1,4 @@
project(service-cellular)
project(service-cellular)
message( "${PROJECT_NAME}  ${CMAKE_CURRENT_LIST_DIR}" )

set(SOURCES


@@ 9,6 9,7 @@ set(SOURCES
    SignalStrength.cpp
    SimCard.cpp
    NetworkSettings.cpp
    PacketData.cpp
    RequestFactory.cpp
    CellularRequestHandler.cpp
    requests/Request.cpp

M module-services/service-cellular/CellularServiceAPI.cpp => module-services/service-cellular/CellularServiceAPI.cpp +34 -5
@@ 253,13 253,11 @@ bool CellularServiceAPI::GetAntenna(sys::Service *serv, bsp::cellular::antenna &
bool CellularServiceAPI::TransmitDtmfTones(sys::Service *serv, uint32_t digit)
{
    auto msg = std::make_shared<CellularDtmfRequestMessage>(digit);

    return sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
}

bool CellularServiceAPI::USSDRequest(sys::Service *serv, CellularUSSDMessage::RequestType type, std::string data)
{

    auto msg = std::make_shared<CellularUSSDMessage>(type, data);
    sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
    return true;


@@ 270,7 268,6 @@ bool CellularServiceAPI::ChangeSimPin(sys::Service *serv,
                                      const std::vector<unsigned int> &passcode,
                                      const std::vector<unsigned int> &pin)
{

    return sys::Bus::SendUnicast(
        std::make_shared<CellularSimPukDataMessage>(sim, passcode, pin), ServiceCellular::serviceName, serv);
}


@@ 280,14 277,46 @@ bool CellularServiceAPI::SetSimCardLock(sys::Service *serv,
                                        CellularSimCardLockDataMessage::SimCardLock lock,
                                        const std::vector<unsigned int> &pin)
{

    return sys::Bus::SendUnicast(
        std::make_shared<CellularSimCardLockDataMessage>(sim, lock, pin), ServiceCellular::serviceName, serv);
}

bool CellularServiceAPI::SetSimCard(sys::Service *serv, Store::GSM::SIM sim)
{

    return sys::Bus::SendUnicast(
        std::make_shared<CellularChangeSimDataMessage>(sim), ServiceCellular::serviceName, serv);
}

bool CellularServiceAPI::GetAPN(sys::Service *serv)
{
    return sys::Bus::SendUnicast(std::make_shared<CellularGetAPNMessage>(), ServiceCellular::serviceName, serv);
}

bool CellularServiceAPI::GetAPN(sys::Service *serv, std::uint8_t contextId)
{
    return sys::Bus::SendUnicast(
        std::make_shared<CellularGetAPNMessage>(contextId), ServiceCellular::serviceName, serv);
}

bool CellularServiceAPI::GetAPN(sys::Service *serv, packet_data::APN::APNType type)
{
    return sys::Bus::SendUnicast(std::make_shared<CellularGetAPNMessage>(type), ServiceCellular::serviceName, serv);
}

bool CellularServiceAPI::SetAPN(sys::Service *serv, packet_data::APN::Config apnConfig)
{
    auto apn = std::make_shared<packet_data::APN::Config>(std::move(apnConfig));
    return sys::Bus::SendUnicast(std::make_shared<CellularSetAPNMessage>(apn), ServiceCellular::serviceName, serv);
}

bool CellularServiceAPI::SetDataTransfer(sys::Service *serv, packet_data::DataTransfer dt)
{
    return sys::Bus::SendUnicast(
        std::make_shared<CellularSetDataTransferMessage>(dt), ServiceCellular::serviceName, serv);
}

bool CellularServiceAPI::GetDataTransfer(sys::Service *serv)
{
    return sys::Bus::SendUnicast(
        std::make_shared<CellularGetDataTransferMessage>(), ServiceCellular::serviceName, serv);
}

M module-services/service-cellular/CellularUrcHandler.cpp => module-services/service-cellular/CellularUrcHandler.cpp +18 -0
@@ 155,6 155,24 @@ void CellularUrcHandler::Handle(Cpin &urc)
    }
}

void CellularUrcHandler::Handle(Qiurc &urc)
{
    auto urcType = urc.getType();
    if (urc.isValid() && urcType) {
        switch (*urcType) {
        case Qiurc::QIUrcMessages::DeactivateContext:
            if (auto urcFirstParam = urc.getFirstParam(); urcFirstParam) {
                int ctxid = 0;
                if (utils::toNumeric(*urcFirstParam, ctxid)) {
                    response = std::make_unique<CellularDeactivateContextResponse>(at::Result::Code::OK, ctxid);
                    urc.setHandled(true);
                }
            }
            break;
        }
    }
}

void CellularUrcHandler::Handle(PoweredDown &urc)
{
    if (urc.isValid()) {

M module-services/service-cellular/CellularUrcHandler.hpp => module-services/service-cellular/CellularUrcHandler.hpp +2 -0
@@ 16,6 16,7 @@
#include <module-cellular/at/UrcPoweredDown.hpp>
#include <module-cellular/at/UrcQind.hpp>
#include <module-cellular/at/UrcResponse.hpp>
#include <module-cellular/at/UrcQiurc.hpp>

using namespace at::urc;



@@ 35,6 36,7 @@ class CellularUrcHandler : public UrcHandler
    void Handle(Ctze &urc) final;
    void Handle(Qind &urc) final;
    void Handle(Cpin &urc) final;
    void Handle(Qiurc &urc) final;
    void Handle(PoweredDown &urc) final;
    void Handle(UrcResponse &urc) final;


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

#include "PacketData.hpp"

#include <optional>
#include <algorithm>
#include <iterator>

#include <response.hpp>
#include <Utils.hpp>
namespace at
{
    namespace response
    {
        bool parseQICSGP(const at::Result &resp, std::shared_ptr<packet_data::APN::Config> retAPN)
        {

            /// AT+QICSGP=<contextID>[,<context_type>,<APN>[,<username>,<password>)[,<authentication>[,<cdma_pwd>]]]]
            /// +QICSGP: <context_type>,<APN>,<username>,<password>,<authentication>
            /// +QICSGP: 1,"","","",0
            constexpr auto AT_QICSGP = "+QICSGP:";
            if (auto tokens = at::response::getTokensForATCommand(resp, AT_QICSGP); tokens) {

                constexpr int QICSGP_TokensCount = 5; /// Could be depend ? on firmware version, assume that always have
                                                      /// <context_type>,<APN>,<username>,<password>,<authentication>
                if ((*tokens).size() == QICSGP_TokensCount) {

                    retAPN->contextType =
                        static_cast<packet_data::APN::ContextType>(utils::getNumericValue<unsigned int>((*tokens)[0]));
                    retAPN->apn = (*tokens)[1];
                    utils::findAndReplaceAll(retAPN->apn, at::response::StringDelimiter, "");
                    retAPN->username = (*tokens)[2];
                    utils::findAndReplaceAll(retAPN->username, at::response::StringDelimiter, "");
                    retAPN->password = (*tokens)[3];
                    utils::findAndReplaceAll(retAPN->password, at::response::StringDelimiter, "");
                    retAPN->authMethod =
                        static_cast<packet_data::APN::AuthMethod>(utils::getNumericValue<unsigned int>((*tokens)[4]));
                    return true;
                }
            }
            return false;
        }

        bool parseQIACT(const at::Result &resp, std::vector<std::shared_ptr<packet_data::APN::Config>> &ret)
        {

            /**
             * In case of AT+QIACT?
             * +QIACT:1,<context_state>,<context_type>[,<IP_address>]
             * [.....
             * +QIACT:16,<context_state>,<context_type>[,<IP_address>]]
             *
             * also could be empty list
             */
            constexpr int QIACT_TokensCount        = 3;
            constexpr int QIACT_WithIP_TokensCount = 4;

            constexpr auto AT_QIACT = "+QIACT:";
            if (auto mtokens = at::response::getTokensForATResults(resp, AT_QIACT); mtokens) {
                for (const auto &tokens : *mtokens) {

                    if (tokens.size() < QIACT_TokensCount) {
                        continue;
                    }
                    std::shared_ptr<packet_data::APN::Config> retAPN = std::make_shared<packet_data::APN::Config>();
                    retAPN->contextId                                = utils::getNumericValue<std::uint8_t>(tokens[0]);
                    retAPN->state =
                        static_cast<packet_data::APN::ContextState>(utils::getNumericValue<std::uint8_t>(tokens[1]));
                    retAPN->contextType =
                        static_cast<packet_data::APN::ContextType>(utils::getNumericValue<std::uint8_t>(tokens[2]));

                    if (tokens.size() >= QIACT_WithIP_TokensCount) {
                        retAPN->ip = tokens[3];
                        utils::findAndReplaceAll(retAPN->ip, at::response::StringDelimiter, "");
                    }
                    ret.push_back(retAPN);
                }
                return true; /// empty list is also good result
            }
            return false;
        }
    } // namespace response
    namespace query
    {
        std::string prepareQICSGP(std::shared_ptr<packet_data::APN::Config> apn, bool setEmpty)
        {
            /// AT+QICSGP=<contextID>[,<context_type>,<APN>[,<username>,<password>)[,<authentication>]]]

            if (setEmpty) {
                LOG_DEBUG("Set empty APN");
                return at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast<int>(apn->contextId)) + "," +
                       "1,\"\",\"\",\"\",1";
            }

            return at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast<int>(apn->contextId)) + "," +
                   utils::to_string(static_cast<int>(apn->contextType)) + ",\"" + apn->apn + "\",\"" + apn->username +
                   "\",\"" + apn->password + "\"," + utils::to_string(static_cast<int>(apn->authMethod));
        }

    } // namespace query
} // namespace at

namespace packet_data
{

    PDPContext::PDPContext(ServiceCellular &cellularService) : cellularService(cellularService)
    {
        channel = cellularService.cmux->get(TS0710::Channel::Commands);
    }

    std::shared_ptr<APN::Config> PDPContext::getConfiguration(std::uint8_t contextId)
    {
        if (channel) {
            auto resp = channel->cmd(at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast<int>(contextId)));
            if (resp.code == at::Result::Code::OK) {
                std::shared_ptr<APN::Config> ret = std::make_shared<APN::Config>();
                ret->contextId                   = contextId;
                if (at::response::parseQICSGP(resp, ret)) {
                    return ret;
                }
            }
        }
        return nullptr;
    }

    at::Result::Code PDPContext::setConfiguration(std::shared_ptr<APN::Config> apn, bool setEmpty)
    {
        if (channel) {
            auto resp = channel->cmd(at::query::prepareQICSGP(apn, setEmpty));
            return resp.code;
        }
        return at::Result::Code::ERROR;
    }

    at::Result::Code PDPContext::activate(std::uint8_t contextId)
    {

        if (channel) {
            auto resp = channel->cmd(at::factory(at::AT::QIACT) + "=" + utils::to_string(static_cast<int>(contextId)));

            /**
             * From quectel documentation:
             * 1. Reboot the module if there is no response in 150s.
             * 2. If failed to deactivate the PDP context for 3 times continuously, then reboot the module.
             */
            return resp.code;
        }
        return at::Result::Code::ERROR;
    }
    at::Result::Code PDPContext::deactivate(std::uint8_t contextId)
    {
        if (channel) {
            /// this command could generate URC, deactivate
            auto resp =
                channel->cmd(at::factory(at::AT::QIDEACT) + "=" + utils::to_string(static_cast<int>(contextId)));

            return resp.code;
        }
        return at::Result::Code::ERROR;
    }

    std::optional<const std::vector<std::shared_ptr<APN::Config>>> PDPContext::getActive()
    {
        if (channel) {
            auto resp = channel->cmd(at::factory(at::AT::QIACT) + "?");

            if (resp.code == at::Result::Code::OK) {
                std::vector<std::shared_ptr<APN::Config>> ret;
                if (at::response::parseQIACT(resp, ret)) {
                    return ret;
                }
            }
        }
        return std::nullopt;
    }

    PacketData::PacketData(ServiceCellular &cellularService) : cellularService(cellularService){};

    void PacketData::loadAPNSettings()
    {

        std::shared_ptr<APN::Config> apnConfig = std::make_shared<APN::Config>();
        apnConfig->contextId                   = 1;
        apnConfig->apnType                     = APN::APNType::Default;
        apnConfig->apn                         = "internet";
        apnConfig->authMethod                  = APN::AuthMethod::NONE;

        contextMap[apnConfig->contextId] = apnConfig;

        LOG_ERROR("loadAPNSettings");
    }

    void PacketData::saveAPNSettings()
    {
        /// Save in phone memory
    }

    at::Result::Code PacketData::updateAPNSettings(std::uint8_t ctxId)
    {
        LOG_DEBUG("updateAPNSettings %d", ctxId);
        PDPContext pdpCtx(cellularService);
        std::shared_ptr<APN::Config> apnConfig;
        std::shared_ptr<APN::Config> modemApn;
        if ((modemApn = pdpCtx.getConfiguration(ctxId)) && (modemApn != nullptr)) {

            auto phoneApn = contextMap.find(ctxId);

            if (phoneApn != contextMap.end()) {
                LOG_DEBUG("Phone context exists");
                if (dataTransfer == DataTransfer::Off) {
                    /// set null configuration, solution based on lack of quectel documentation
                    return pdpCtx.setConfiguration(phoneApn->second, true);
                }
                else {
                    if (!phoneApn->second->compare(modemApn)) {
                        /// rebuild configuratio
                        LOG_DEBUG("Update modem context %d", ctxId);
                        return pdpCtx.setConfiguration(phoneApn->second);
                    }
                }
            }
            else {
                LOG_ERROR("Phone context not exists");
                if (!modemApn->isEmpty()) {
                    /** update phone configuration base on modem conf (eg. ims)
                     *
                     * Comment from quectel (2020.12):
                     * As I know, only VZW MBN would use IMS on CID 1 to register IMS service directly.
                     * So we usually ask customers to set up data connection on CID3 for VZW sim card.
                     * Regarding other MBN files, the CID 1 shouldn’t be IMS because the CID1 is used
                     * to activate default bearer for LTE network. So we usually ask customers to
                     * configure their own APN on CID1.  With this rule, it’s easy for customer
                     * to configure their APN no matter which MBN file is activated
                     */
                    LOG_ERROR("Update modem context %d", ctxId);
                    return pdpCtx.setConfiguration(modemApn, true);
                }
            }
        }
        return at::Result::Code::OK;
    }
    void PacketData::setupAPNSettings()
    {
        for (std::uint8_t ctxId = MINContextId; ctxId <= MAXContextId; ctxId++) {
            updateAPNSettings(ctxId);
        }
    }

    std::optional<std::shared_ptr<APN::Config>> PacketData::getAPN(std::uint8_t ctxid)
    {
        auto apn = std::find_if(contextMap.begin(), contextMap.end(), [&ctxid](const ContextPair &pair) {
            return pair.second->contextId == ctxid;
        });

        if (apn != contextMap.end()) {
            return apn->second;
        }

        return std::nullopt;
    }

    const std::vector<std::shared_ptr<APN::Config>> PacketData::getAPNs() const
    {
        std::vector<std::shared_ptr<APN::Config>> vconf;
        std::transform(
            contextMap.begin(), contextMap.end(), std::back_inserter(vconf), [](auto &cm) { return cm.second; });
        return vconf;
    }

    std::optional<std::shared_ptr<APN::Config>> PacketData::getAPNFirst(APN::APNType type)
    {

        auto apn = std::find_if(contextMap.begin(), contextMap.end(), [&type](const ContextPair &pair) -> bool {
            return pair.second->apnType == type;
        });

        if (apn != contextMap.end()) {
            return apn->second;
        }

        return std::nullopt;
    }

    at::Result::Code PacketData::setAPN(std::shared_ptr<APN::Config> apn)
    {
        contextMap[apn->contextId] = apn;
        return updateAPNSettings(apn->contextId);
    }

    bool PacketData::setDataTransfer(DataTransfer dt)
    {
        dataTransfer = dt;
        setupAPNSettings();
        return true;
    }
    DataTransfer PacketData::getDataTransfer() const
    {
        return dataTransfer;
    }

    std::optional<const std::vector<std::shared_ptr<APN::Config>>> PacketData::getActiveContexts()
    {
        PDPContext pdpCtx(cellularService);
        return pdpCtx.getActive();
    }
    at::Result::Code PacketData::activateContext(std::uint8_t contextId)
    {
        PDPContext pdpCtx(cellularService);
        return pdpCtx.activate(contextId);
    }
    at::Result::Code PacketData::deactivateContext(std::uint8_t contextId)
    {
        PDPContext pdpCtx(cellularService);
        return pdpCtx.deactivate(contextId);
    }
} // namespace packet_data

A module-services/service-cellular/PacketData.hpp => module-services/service-cellular/PacketData.hpp +104 -0
@@ 0,0 1,104 @@
// 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 <unordered_map>
#include "PacketData.hpp"

#include "service-cellular/PacketDataTypes.hpp"
#include "service-cellular/ServiceCellular.hpp"

namespace at
{
    namespace response
    {
        /// Separate, specific AT command for quectel, only for TCP/PDP connection/configuration

        bool parseQICSGP(const at::Result &resp, std::shared_ptr<packet_data::APN::Config> retAPN);
        bool parseQIACT(const at::Result &resp, std::vector<std::shared_ptr<packet_data::APN::Config>> &ret);

    } // namespace response
    namespace query
    {
        std::string prepareQICSGP(std::shared_ptr<packet_data::APN::Config> apn, bool setEmpty = false);
    }
} // namespace at

namespace packet_data
{

    class PDPContext
    {

      private:
        ServiceCellular &cellularService;
        DLC_channel *channel = nullptr;

      public:
        explicit PDPContext(ServiceCellular &cellularService);
        at::Result::Code setConfiguration(std::shared_ptr<APN::Config> apn, bool setEmpty = false);
        std::shared_ptr<APN::Config> getConfiguration(std::uint8_t contextId);
        at::Result::Code activate(std::uint8_t contextId);
        at::Result::Code deactivate(std::uint8_t contextId);
        std::optional<const std::vector<std::shared_ptr<APN::Config>>> getActive();
    };

    /**
     * @brief wrapping class, transfer data (APN) settings. Responsible for maintaining the state
     * between the modem and the settings
     */
    class PacketData
    {
      private:
        ServiceCellular &cellularService;
        ContextMap contextMap;
        DataTransfer dataTransfer = DataTransfer::On; /// depend on APN settings

      public:
        explicit PacketData(ServiceCellular &cellularService);
        /**
         * @brief load APN settings from phone memory, not call modem func.
         *
         * To synchronize with modem, this function should be call as soon as
         * the modem is ready (in sense of AT commands). Then should be call
         * setupAPNSettings to synchronize modem database with phone database.
         */
        void loadAPNSettings();

        /**
         * @brief save all APN's in phone memory
         */
        void saveAPNSettings();

        /**
         * @brief setup APN on modem, based on configuration loaded in loadAPNSettings
         * cleanup old configuration
         */
        void setupAPNSettings();

        at::Result::Code updateAPNSettings(std::uint8_t ctxId);

        std::optional<const std::vector<std::shared_ptr<APN::Config>>> getActiveContexts();
        at::Result::Code activateContext(std::uint8_t contextId);
        at::Result::Code deactivateContext(std::uint8_t contextId);

        bool setDataTransfer(DataTransfer dt);
        DataTransfer getDataTransfer() const;
        /**
         * @brief get one APN from phone configuration, connected with ctxid
         */
        std::optional<std::shared_ptr<APN::Config>> getAPN(std::uint8_t ctxid);
        /**
         * @brief get all APNs from phone configuration
         */
        const std::vector<std::shared_ptr<APN::Config>> getAPNs() const;
        /**
         * @brief get first APN with type, from phone configuration
         */
        std::optional<std::shared_ptr<APN::Config>> getAPNFirst(APN::APNType type);
        at::Result::Code setAPN(std::shared_ptr<APN::Config> apn);
    };

} // namespace packet_data

M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +110 -1
@@ 102,6 102,8 @@ const char *State::c_str(State::ST state)
        return "PowerUpProcedure";
    case ST::AudioConfigurationProcedure:
        return "AudioConfigurationProcedure";
    case ST::APNConfProcedure:
        return "APNConfProcedure";
    case ST::ModemOn:
        return "ModemOn";
    case ST::URCReady:


@@ 196,6 198,9 @@ ServiceCellular::ServiceCellular() : sys::Service(serviceName, "", cellularStack
        sys::Bus::SendMulticast(msg.value(), sys::BusChannels::ServiceCellularNotifications, this);
    };
    registerMessageHandlers();

    packetData = std::make_unique<packet_data::PacketData>(*this);
    packetData->loadAPNSettings();
}

ServiceCellular::~ServiceCellular()


@@ 259,8 264,10 @@ sys::ReturnCodes ServiceCellular::SwitchPowerModeHandler(const sys::ServicePower

    return sys::ReturnCodes::Success;
}

void handleCellularSimNewPinDataMessage(CellularSimNewPinDataMessage *msg)
{}

void ServiceCellular::registerMessageHandlers()
{
    connect(typeid(CellularSimNewPinDataMessage), [&](sys::Message *request) -> sys::MessagePointer {


@@ 287,6 294,40 @@ void ServiceCellular::registerMessageHandlers()
        auto msg = static_cast<CellularStartOperatorsScanMessage *>(request);
        return handleCellularStartOperatorsScan(msg);
    });
    connect(typeid(CellularGetActiveContextsMessage), [&](sys::Message *request) -> sys::MessagePointer {
        auto msg = static_cast<CellularGetActiveContextsMessage *>(request);
        return handleCellularGetActiveContextsMessage(msg);
    });

    connect(typeid(CellularGetAPNMessage), [&](sys::Message *request) -> sys::MessagePointer {
        auto msg = static_cast<CellularGetAPNMessage *>(request);
        return handleCellularGetAPNMessage(msg);
    });

    connect(typeid(CellularSetDataTransferMessage), [&](sys::Message *request) -> sys::MessagePointer {
        auto msg = static_cast<CellularSetDataTransferMessage *>(request);
        return handleCellularSetDataTransferMessage(msg);
    });

    connect(typeid(CellularGetDataTransferMessage), [&](sys::Message *request) -> sys::MessagePointer {
        auto msg = static_cast<CellularGetDataTransferMessage *>(request);
        return handleCellularGetDataTransferMessage(msg);
    });

    connect(typeid(CellularActivateContextMessage), [&](sys::Message *request) -> sys::MessagePointer {
        auto msg = static_cast<CellularActivateContextMessage *>(request);
        return handleCellularActivateContextMessage(msg);
    });

    connect(typeid(CellularDeactivateContextMessage), [&](sys::Message *request) -> sys::MessagePointer {
        auto msg = static_cast<CellularDeactivateContextMessage *>(request);
        return handleCellularDeactivateContextMessage(msg);
    });

    connect(typeid(CellularGetActiveContextsMessage), [&](sys::Message *request) -> sys::MessagePointer {
        auto msg = static_cast<CellularGetActiveContextsMessage *>(request);
        return handleCellularGetActiveContextsMessage(msg);
    });
    handle_CellularGetChannelMessage();
}



@@ 342,6 383,9 @@ void ServiceCellular::change_state(cellular::StateChange *msg)
    case State::ST::CellularConfProcedure:
        handle_start_conf_procedure();
        break;
    case State::ST::APNConfProcedure:
        handle_apn_conf_procedure();
        break;
    case State::ST::SanityCheck:
        handle_sim_sanity_check();
        break;


@@ 531,7 575,7 @@ bool ServiceCellular::handle_audio_conf_procedure()
                LOG_DEBUG("Setting up notifications callback");
                notificationsChannel->setCallback(notificationCallback);
            }
            state.set(this, State::ST::SanityCheck);
            state.set(this, State::ST::APNConfProcedure);
            return true;
        }
        else {


@@ 1991,3 2035,68 @@ std::shared_ptr<cellular::RawCommandRespAsync> ServiceCellular::handleCellularSt
    sys::Bus::SendUnicast(ret, msg->sender, this);
    return ret;
}
bool ServiceCellular::handle_apn_conf_procedure()
{
    LOG_DEBUG("APN on modem configuration");
    packetData->setupAPNSettings();
    state.set(this, State::ST::SanityCheck);
    return true;
}

std::shared_ptr<CellularGetAPNResponse> ServiceCellular::handleCellularGetAPNMessage(CellularGetAPNMessage *msg)
{
    std::vector<std::shared_ptr<packet_data::APN::Config>> apns;

    if (auto type = msg->getAPNType(); type) {
        if (auto apn = packetData->getAPNFirst(*type); apn) {
            apns.push_back(*apn);
        }
        return std::make_shared<CellularGetAPNResponse>(apns);
    }

    if (auto ctxid = msg->getContextId(); ctxid) {
        if (auto apn = packetData->getAPN(*ctxid); apn) {
            apns.push_back(*apn);
        }
        return std::make_shared<CellularGetAPNResponse>(apns);
    }

    return std::make_shared<CellularGetAPNResponse>(packetData->getAPNs());
}
std::shared_ptr<CellularSetAPNResponse> ServiceCellular::handleCellularSetAPNMessage(CellularSetAPNMessage *msg)
{

    auto apn = msg->getAPNConfig();

    return std::make_shared<CellularSetAPNResponse>(packetData->setAPN(apn));
}
std::shared_ptr<CellularSetDataTransferResponse> ServiceCellular::handleCellularSetDataTransferMessage(
    CellularSetDataTransferMessage *msg)
{
    packetData->setDataTransfer(msg->getDataTransfer());
    return std::make_shared<CellularSetDataTransferResponse>(at::Result::Code::OK);
}
std::shared_ptr<CellularGetDataTransferResponse> ServiceCellular::handleCellularGetDataTransferMessage(
    CellularGetDataTransferMessage *msg)
{
    return std::make_shared<CellularGetDataTransferResponse>(packetData->getDataTransfer());
}

std::shared_ptr<CellularActivateContextResponse> ServiceCellular::handleCellularActivateContextMessage(
    CellularActivateContextMessage *msg)
{
    return std::make_shared<CellularActivateContextResponse>(packetData->activateContext(msg->getContextId()),
                                                             msg->getContextId());
}
std::shared_ptr<CellularDeactivateContextResponse> ServiceCellular::handleCellularDeactivateContextMessage(
    CellularDeactivateContextMessage *msg)
{
    return std::make_shared<CellularDeactivateContextResponse>(packetData->deactivateContext(msg->getContextId()),
                                                               msg->getContextId());
}

std::shared_ptr<CellularGetActiveContextsResponse> ServiceCellular::handleCellularGetActiveContextsMessage(
    CellularGetActiveContextsMessage *msg)
{
    return std::make_shared<CellularGetActiveContextsResponse>(packetData->getActiveContexts());
}

M module-services/service-cellular/service-cellular/CellularServiceAPI.hpp => module-services/service-cellular/service-cellular/CellularServiceAPI.hpp +18 -0
@@ 4,6 4,7 @@
#pragma once

#include "CellularMessage.hpp"
#include "PacketDataCellularMessage.hpp"

#include <Modem/TS0710/TS0710.h>
#include <PhoneNumber.hpp>


@@ 80,4 81,21 @@ namespace CellularServiceAPI
                        CellularSimCardLockDataMessage::SimCardLock lock,
                        const std::vector<unsigned int> &pin);
    bool SetSimCard(sys::Service *serv, Store::GSM::SIM sim);

    /**
     * @brief get all APNs from phone configuration
     */
    bool GetAPN(sys::Service *serv);
    /**
     * @brief get one APN from phone configuration, connected with ctxid
     */
    bool GetAPN(sys::Service *serv, std::uint8_t contextId);
    /**
     * @brief get first APN with type, from phone configuration
     */
    bool GetAPN(sys::Service *serv, packet_data::APN::APNType type);

    bool SetAPN(sys::Service *serv, packet_data::APN::Config apnConfig);
    bool SetDataTransfer(sys::Service *serv, packet_data::DataTransfer dt);
    bool GetDataTransfer(sys::Service *serv);
}; // namespace CellularServiceAPI

A module-services/service-cellular/service-cellular/PacketDataCellularMessage.hpp => module-services/service-cellular/service-cellular/PacketDataCellularMessage.hpp +224 -0
@@ 0,0 1,224 @@
// 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 <optional>
#include "CellularMessage.hpp"
#include "MessageType.hpp"

#include "PacketDataTypes.hpp"
#include "Result.hpp"

class CellularGetAPNMessage : public CellularMessage
{
    std::optional<std::uint8_t> contextId            = std::nullopt;
    std::optional<packet_data::APN::APNType> apnType = std::nullopt;

  public:
    CellularGetAPNMessage() : CellularMessage(MessageType::CellularPacketData)
    {}
    CellularGetAPNMessage(std::uint8_t contextId)
        : CellularMessage(MessageType::CellularPacketData), contextId(contextId)
    {}
    CellularGetAPNMessage(packet_data::APN::APNType apnType)
        : CellularMessage(MessageType::CellularPacketData), apnType(apnType)
    {}
    [[nodiscard]] const std::optional<packet_data::APN::APNType> getAPNType() const noexcept
    {
        return apnType;
    }
    [[nodiscard]] const std::optional<std::uint8_t> getContextId() const noexcept
    {
        return contextId;
    }
};

class CellularSetAPNMessage : public CellularMessage // also as DeleteAPN
{
    std::shared_ptr<packet_data::APN::Config> apnConfig;

  public:
    CellularSetAPNMessage(std::shared_ptr<packet_data::APN::Config> apnConfig)
        : CellularMessage(MessageType::CellularPacketData), apnConfig(apnConfig)
    {}

    [[nodiscard]] std::shared_ptr<packet_data::APN::Config> getAPNConfig() const noexcept
    {
        return apnConfig;
    }
};

class CellularSetDataTransferMessage : public CellularMessage
{
    packet_data::DataTransfer dataTransfer;

  public:
    CellularSetDataTransferMessage(packet_data::DataTransfer dataTransfer)
        : CellularMessage(MessageType::CellularPacketData), dataTransfer(dataTransfer)
    {}
    [[nodiscard]] packet_data::DataTransfer getDataTransfer() const noexcept
    {
        return dataTransfer;
    }
};

class CellularGetDataTransferMessage : public CellularMessage
{
  public:
    CellularGetDataTransferMessage() : CellularMessage(MessageType::CellularPacketData)
    {}
};

class CellularGetActiveContextsMessage : public CellularMessage
{
  public:
    CellularGetActiveContextsMessage() : CellularMessage(MessageType::CellularPacketData)
    {}
};

class CellularActivateContextMessage : public CellularMessage
{
    std::uint8_t contextId;

  public:
    CellularActivateContextMessage(std::uint8_t contextId)
        : CellularMessage(MessageType::CellularPacketData), contextId(contextId)
    {}
    [[nodiscard]] std::uint8_t getContextId() const noexcept
    {
        return contextId;
    }
};
class CellularDeactivateContextMessage : public CellularMessage
{
    std::uint8_t contextId;

  public:
    CellularDeactivateContextMessage(std::uint8_t contextId)
        : CellularMessage(MessageType::CellularPacketData), contextId(contextId)
    {}
    [[nodiscard]] std::uint8_t getContextId() const noexcept
    {
        return contextId;
    }
};

// Send from Cellular
class CellularGetAPNResponse : public CellularMessage
{
    std::vector<std::shared_ptr<packet_data::APN::Config>> apns;

  public:
    CellularGetAPNResponse(std::vector<std::shared_ptr<packet_data::APN::Config>> apns)
        : CellularMessage(MessageType::CellularPacketData), apns(std::move(apns))
    {}

    [[nodiscard]] const std::vector<std::shared_ptr<packet_data::APN::Config>> &getAPNs() const noexcept
    {
        return apns;
    }
};

class CellularATResponse : public CellularMessage
{
    at::Result::Code result;

  public:
    CellularATResponse(at::Result::Code result) : CellularMessage(MessageType::CellularPacketData), result(result)
    {}

    [[nodiscard]] at::Result::Code getResult() const noexcept
    {
        return result;
    }
};

class CellularSetAPNResponse : public CellularATResponse
{
  public:
    CellularSetAPNResponse(at::Result::Code result) : CellularATResponse(result)
    {}
};

class CellularSetDataTransferResponse : public CellularATResponse
{
  public:
    CellularSetDataTransferResponse(at::Result::Code result) : CellularATResponse(result)
    {}
};

class CellularGetDataTransferResponse : public CellularMessage
{
    packet_data::DataTransfer dataTransfer;

  public:
    CellularGetDataTransferResponse(packet_data::DataTransfer dataTransfer)
        : CellularMessage(MessageType::CellularPacketData), dataTransfer(dataTransfer)
    {}

    [[nodiscard]] packet_data::DataTransfer getDataTransfer() const noexcept
    {
        return dataTransfer;
    }
};

class CellularGetActiveContextsResponse : public CellularMessage
{
    std::optional<std::vector<std::shared_ptr<packet_data::APN::Config>>> result;

  public:
    CellularGetActiveContextsResponse(std::optional<std::vector<std::shared_ptr<packet_data::APN::Config>>> result)
        : CellularMessage(MessageType::CellularPacketData)
    {
        if (result) {
            result = std::move(*result);
        }
        else {
            result = std::nullopt;
        }
    }

    [[nodiscard]] std::optional<const std::vector<std::shared_ptr<packet_data::APN::Config>>> getActive()
    {
        return result;
    }
};

class CellularActivateContextResponse : public CellularATResponse
{
    std::uint8_t contextId;

  public:
    CellularActivateContextResponse(at::Result::Code result, std::uint8_t contextId)
        : CellularATResponse(result), contextId(contextId)
    {}
    [[nodiscard]] std::uint8_t getContextId() const noexcept
    {
        return contextId;
    }
};
class CellularDeactivateContextResponse : public CellularATResponse
{
    std::uint8_t contextId;
    bool isUrc;

  public:
    CellularDeactivateContextResponse(at::Result::Code result, std::uint8_t contextId, bool isUrc = false)
        : CellularATResponse(result), contextId(contextId), isUrc(isUrc)
    {}

    [[nodiscard]] std::uint8_t getContextId() const noexcept
    {
        return contextId;
    }
    /**
     * @breif inform that message come from URC (eg. because call AT+CFUN=0, reset by BTS, ...)
     * @return
     */
    [[nodiscard]] bool isURC()
    {
        return isUrc;
    }
};

A module-services/service-cellular/service-cellular/PacketDataTypes.hpp => module-services/service-cellular/service-cellular/PacketDataTypes.hpp +117 -0
@@ 0,0 1,117 @@
// 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 <memory>

namespace packet_data
{

    enum class DataTransfer
    {
        Off,
        On
    };

    namespace APN
    {
        /**
         * @brief Defines APN types
         */
        enum class APNType
        {
            Default, ///< for data traffic
            IMS,     ///< IP Multimedia Subsystem for eg VoLTE
            MMS,     ///< for MMS service
            Fota     ///< for Firmware Update
        };

        /**
         * @brief Defines ip address type as in "TCP(IP) AT Commands Manual v1.1" from 2019-10-28
         */
        enum class ContextType : unsigned char
        {
            ipv4   = 1,
            ipv6   = 2, ///< note, 2 was for ipv4v6 in "TCP(IP) AT Commands Manual v1.0"
            ipv4v6 = 3
        };

        /**
         * @brief APN Authorization method
         */
        enum class AuthMethod : unsigned char
        {
            NONE = 0,
            PAP  = 1,
            CHAP = 2,
            AUTO = 3,
        };

        /**
         * @brief To configure whether to save <username> and <password> over CDMA network.
         */
        enum class SaveCDMAPwd : unsigned char
        {
            Disable = 0,
            Enable  = 1
        };

        enum class ContextState : unsigned char
        {
            Deactivated = 0,
            Activated   = 1
        };

        /**
         * @brief APN configuration data
         */
        class Config
        {
          public:
            unsigned char contextId =
                0; /// context on which apn is configured available values 1-16, 0 - means not set yet
            APNType apnType         = APNType::Default;
            ContextState state      = ContextState::Deactivated;
            ContextType contextType = ContextType::ipv4; /// IP type
            AuthMethod authMethod   = AuthMethod::NONE;
            std::string apn; /// name of the APN
            std::string username;
            std::string password;
            std::string ip; /// set after connection

            bool isEmpty() const noexcept
            {
                return apn.empty();
            }

            bool compare(std::shared_ptr<Config> c2)
            {
                return (this->apn == c2->apn) && (this->contextId == c2->contextId) &&
                       (this->contextType == c2->contextType) && (this->password == c2->password) &&
                       (this->username == c2->username) && (this->authMethod == c2->authMethod);
            }
            /**
             * @brief Compare two APNs only by elements essential for modem
             */
            friend bool operator==(const Config &c1, const Config &c2)
            {
                return (c1.apn == c2.apn) && (c1.contextId == c2.contextId) && (c1.contextType == c2.contextType) &&
                       (c1.password == c2.password) && (c1.username == c2.username) && (c1.authMethod == c2.authMethod);
            }

            friend bool operator!=(const Config &c1, const Config &c2)
            {
                return !(c1 == c2);
            }
        };

    } // namespace APN

    constexpr unsigned char MINContextId = 1;
    constexpr unsigned char MAXContextId = 16;
    using ContextMap                     = std::unordered_map<unsigned char, std::shared_ptr<APN::Config>>;
    using ContextPair                    = std::pair<unsigned char, std::shared_ptr<APN::Config>>;

} // namespace packet_data

M module-services/service-cellular/service-cellular/ServiceCellular.hpp => module-services/service-cellular/service-cellular/ServiceCellular.hpp +24 -0
@@ 9,6 9,8 @@
#include "CellularMessage.hpp"
#include "State.hpp"
#include "USSD.hpp"
#include "PacketData.hpp"
#include "PacketDataCellularMessage.hpp"

#include <Modem/TS0710/DLC_channel.h>
#include <Modem/TS0710/TS0710.h>


@@ 46,6 48,11 @@ namespace sys
    class Timer;
} // namespace sys

namespace packet_data
{
    class PacketData;
    class PDPContext;
} // namespace packet_data
class ServiceCellular : public sys::Service
{



@@ 159,6 166,8 @@ class ServiceCellular : public sys::Service
    void CallStateTimerHandler();
    DLC_channel::Callback_t notificationCallback = nullptr;

    std::unique_ptr<packet_data::PacketData> packetData;

    cellular::State state;
    bsp::Board board = bsp::Board::none;



@@ 221,6 230,7 @@ class ServiceCellular : public sys::Service
    bool handle_fatal_failure();
    bool handle_ready();
    std::unique_ptr<settings::Settings> settings = std::make_unique<settings::Settings>(this);
    bool handle_apn_conf_procedure();

    bool handleAllMessagesFromMessageStorage();
    [[nodiscard]] SMSRecord createSMSRecord(const UTF8 &decodedMessage,


@@ 260,8 270,22 @@ class ServiceCellular : public sys::Service
    std::shared_ptr<cellular::RawCommandRespAsync> handleCellularStartOperatorsScan(
        CellularStartOperatorsScanMessage *msg);

    std::shared_ptr<CellularGetAPNResponse> handleCellularGetAPNMessage(CellularGetAPNMessage *msg);
    std::shared_ptr<CellularSetAPNResponse> handleCellularSetAPNMessage(CellularSetAPNMessage *msg);
    std::shared_ptr<CellularSetDataTransferResponse> handleCellularSetDataTransferMessage(
        CellularSetDataTransferMessage *msg);
    std::shared_ptr<CellularGetDataTransferResponse> handleCellularGetDataTransferMessage(
        CellularGetDataTransferMessage *msg);
    std::shared_ptr<CellularActivateContextResponse> handleCellularActivateContextMessage(
        CellularActivateContextMessage *msg);
    std::shared_ptr<CellularDeactivateContextResponse> handleCellularDeactivateContextMessage(
        CellularDeactivateContextMessage *msg);
    std::shared_ptr<CellularGetActiveContextsResponse> handleCellularGetActiveContextsMessage(
        CellularGetActiveContextsMessage *msg);
    friend class CellularUrcHandler;
    friend class SimCard;
    friend class CellularRequestHandler;
    friend class NetworkSettings;
    friend class packet_data::PDPContext;
    friend class packet_data::PacketData;
};

M module-services/service-cellular/service-cellular/State.hpp => module-services/service-cellular/service-cellular/State.hpp +1 -0
@@ 18,6 18,7 @@ namespace cellular
            PowerUpInProgress,           /// waiting for modem powered up by polling various bauds
            CellularConfProcedure,       /// configuration procedure
            AudioConfigurationProcedure, /// audio configuration for modem (could be in ModemConfiguration)
            APNConfProcedure,            /// Configure APN set by user, check if modem have similar
            SanityCheck,                 /// prior to ModemOn last sanity checks for one time configurations etc
            ModemOn,   /// modem ready - indicates that modem is fully configured, ( **SIM is not yet configured** )
            URCReady,  /// State indicates that URC handling is enabled

M module-services/service-cellular/tests/CMakeLists.txt => module-services/service-cellular/tests/CMakeLists.txt +9 -0
@@ 18,3 18,12 @@ add_catch2_executable(
        module-cellular
)


add_catch2_executable(
        NAME
        cellular-datatransfer
        SRCS
        unittest_datatransfer.cpp
        LIBS
        module-cellular
)
\ No newline at end of file

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

#define CATCH_CONFIG_MAIN

#include <catch2/catch.hpp>

#include "Result.hpp"
#include <service-cellular/PacketData.hpp>
#include <service-cellular/PacketDataTypes.hpp>

using namespace cellular;

TEST_CASE("Quectel AT DataTransfer commands")
{
    SECTION("QIACT")
    {
        std::vector<std::shared_ptr<packet_data::APN::Config>> ret;
        at::Result resp;

        // OK
        resp.code = at::Result::Code::OK;
        resp.response.push_back("+QIACT: 1,1,1,\"10.7.157.1\"");
        REQUIRE(resp.code == at::Result::Code::OK);
        REQUIRE(at::response::parseQIACT(resp, ret) == true);
        REQUIRE(ret.size() == 1);
        REQUIRE(ret.at(0)->ip == "10.7.157.1");

        ret.clear();
        // Additional element, should be omitted, also other wrong data
        resp.response.push_back("+QIACT:");
        REQUIRE(at::response::parseQIACT(resp, ret) == true);
        REQUIRE(ret.size() == 1);
        REQUIRE(ret.at(0)->ip == "10.7.157.1");

        ret.clear();
        resp.response.clear();
        // Empty return, should return empty list
        REQUIRE(at::response::parseQIACT(resp, ret) == true);
        REQUIRE(ret.size() == 0);

        ret.clear();
        resp.response.clear();
        // Wrong should return zero
        resp.response.push_back("+QIACT: sa sa+QIACT: fsf fsa");
        REQUIRE(at::response::parseQIACT(resp, ret) == true);
        REQUIRE(ret.size() == 0);
    }
    SECTION("QICSGP")
    {
        /// +QICSGP: 1,"","","",0
        std::shared_ptr<packet_data::APN::Config> ret = std::make_shared<packet_data::APN::Config>();
        at::Result resp;
        resp.code = at::Result::Code::OK;
        // OK
        resp.response.push_back("+QICSGP: 1,\"apn\",\"\",\"\",0");
        REQUIRE(resp.code == at::Result::Code::OK);
        REQUIRE(at::response::parseQICSGP(resp, ret) == true);
        REQUIRE(ret->apn == "apn");

        resp.response.clear();
        // OK
        resp.response.push_back("+QICSGP: 1,\"apn\",\"internet\",\"password\",1");
        REQUIRE(resp.code == at::Result::Code::OK);
        REQUIRE(at::response::parseQICSGP(resp, ret) == true);
        REQUIRE(ret->apn == "apn");
        REQUIRE(ret->username == "internet");
        REQUIRE(ret->password == "password");

        resp.response.clear();
        // empty APN
        resp.response.push_back("+QICSGP: 1,\"\",\"\",\"\",0");
        REQUIRE(at::response::parseQICSGP(resp, ret) == true);
        REQUIRE(ret->apn == "");
        REQUIRE(ret->isEmpty() == true);

        // bad response
        resp.code = at::Result::Code::ERROR;
        REQUIRE(at::response::parseQICSGP(resp, ret) == false);

        resp.response.clear();
        // Bad token
        resp.response.push_back("+QICSXX: 1,\"\",\"\",\"\",0");
        REQUIRE(at::response::parseQICSGP(resp, ret) == false);

        resp.response.clear();
        // Not enought parameters
        resp.response.push_back("+QICSGP: 1,\"\",\"\"");
        REQUIRE(at::response::parseQICSGP(resp, ret) == false);

        resp.response.clear();
        // wrong msg
        resp.response.push_back("ds +QICSGP: adsad +QICSGP: 1,\"\",\"");
        REQUIRE(at::response::parseQICSGP(resp, ret) == false);
    }
}

M module-services/service-fota/FotaUrcHandler.hpp => module-services/service-fota/FotaUrcHandler.hpp +1 -0
@@ 26,6 26,7 @@ class FotaUrcHandler : public UrcHandler
    virtual void Handle(Cusd &urc){};
    virtual void Handle(Ctze &urc){};
    virtual void Handle(Cpin &urc){};
    virtual void Handle(Qiurc &urc){};
    virtual void Handle(PoweredDown &urc){};
    virtual void Handle(UrcResponse &urc){};


M source/MessageType.hpp => source/MessageType.hpp +2 -0
@@ 80,6 80,8 @@ enum class MessageType
    CellularSimResponse,         // Send to PIN window (show, error state, hide)
    CellularSimVerifyPinRequest, // Send from PIN window with PIN, PUK, ... number

    CellularPacketData, ///< for all PacketData messages

    CellularGetOwnNumber,
    CellularGetIMSI,
    CellularGetNetworkInfo,