~aleteoryx/muditaos

26c032128a4727b701d2e11732cbbf51fe90cc1c — Hubert Chrzaniuk 5 years ago 65a8cc1
[EGD-4413] New MMI codes support (#1051)

 * added support for CLIP, CLIR, COLP and call waiting
 * fixed service groups according to GSM technical data
M changelog.md => changelog.md +6 -0
@@ 1,5 1,11 @@
# MuditaOS changelog

## [current release]

### Added

* `[cellular]` Added CLIR, CLIP, COLP, call waiting MMI support

## [0.47.1 2020-11-20]

### Added

M module-cellular/at/Commands.hpp => module-cellular/at/Commands.hpp +29 -7
@@ 16,6 16,7 @@ namespace at
    inline const uint32_t default_timeout = 5000; /// if unsure - take this
    inline const uint32_t default_doc_timeout =
        300; /// if you've checked it's ok - or it was at least 300 in code somewhere, take this
    inline const uint32_t default_long_doc_timeout = 15000;

    /// at::Cmd structure with command, it's timeout and some runtime data
    /// { command, timeout, last : {sent, response, status } }


@@ 137,9 138,19 @@ namespace at
        GET_IMEI,
        CCFC, /// Supplementary Services - Call Forwarding Number and Conditions Control
        CCWA, /// Supplementary Services - Call Waiting Control
        CCWA_GET,
        CHLD, /// Supplementary Services - Call Related Supplementary Services
        CLIP, /// Supplementary Services - Calling Line Identification Restriction
        CLIP_GET,
        CLIR, /// Supplementary Services - Calling Line Identification Restriction
        CLIR_GET,
        CLIR_ENABLE,
        CLIR_DISABLE,
        CLIR_RESET,
        COLP, /// Supplementary Services - Connected Line Identification Presentation
        COLP_GET,
        COLP_ENABLE,
        COLP_DISABLE,
        CSSN, /// Supplementary Services - Supplementary Service Notifications
    };



@@ 183,11 194,11 @@ namespace at
            {AT::ATD, {"ATD"}},
            {AT::IPR, {"AT+IPR="}},
            {AT::CMUX, {"AT+CMUX="}},
            {AT::CFUN, {"AT+CFUN=", 15000}},
            {AT::CFUN_RESET, {"AT+CFUN=1,1", 15000}},
            {AT::CFUN_MIN_FUNCTIONALITY, {"AT+CFUN=0", 15000}},
            {AT::CFUN_FULL_FUNCTIONALITY, {"AT+CFUN=1", 15000}},
            {AT::CFUN_DISABLE_TRANSMITTING, {"AT+CFUN=4", 15000}},
            {AT::CFUN, {"AT+CFUN=", default_long_doc_timeout}},
            {AT::CFUN_RESET, {"AT+CFUN=1,1", default_long_doc_timeout}},
            {AT::CFUN_MIN_FUNCTIONALITY, {"AT+CFUN=0", default_long_doc_timeout}},
            {AT::CFUN_FULL_FUNCTIONALITY, {"AT+CFUN=1", default_long_doc_timeout}},
            {AT::CFUN_DISABLE_TRANSMITTING, {"AT+CFUN=4", default_long_doc_timeout}},
            {AT::CMGS, {"AT+CMGS=\""}},
            {AT::QCMGS, {"AT+QCMGS=\""}},
            {AT::CREG, {"AT+CREG?", default_doc_timeout}},


@@ 223,9 234,20 @@ namespace at
            {AT::LIST_MESSAGES, {"AT+CMGL=\"ALL\"", default_doc_timeout}},
            {AT::GET_IMEI, {"AT+GSN", default_doc_timeout}},
            {AT::CCFC, {"AT+CCFC=", default_doc_timeout}},
            {AT::CCWA, {"AT+CCWA=\"", default_doc_timeout}},
            {AT::CCWA, {"AT+CCWA=", default_doc_timeout}},
            {AT::CCWA_GET, {"AT+CCWA?", default_doc_timeout}},
            {AT::CHLD, {"AT+CHLD=\"", default_doc_timeout}},
            {AT::COLP, {"AT+COLP=\"", default_doc_timeout}},
            {AT::CLIP, {"AT+CLIP=", default_long_doc_timeout}},
            {AT::CLIP_GET, {"AT+CLIP?", default_long_doc_timeout}},
            {AT::CLIR, {"AT+CLIR=", default_long_doc_timeout}},
            {AT::CLIR_GET, {"AT+CLIR?", default_long_doc_timeout}},
            {AT::CLIR_RESET, {"AT+CLIR=0", default_long_doc_timeout}},
            {AT::CLIR_ENABLE, {"AT+CLIR=1", default_long_doc_timeout}},
            {AT::CLIR_DISABLE, {"AT+CLIR=2", default_long_doc_timeout}},
            {AT::COLP, {"AT+COLP", default_long_doc_timeout}},
            {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}}};

        if (fact.count(at)) {

M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +5 -0
@@ 18,8 18,13 @@ set(SOURCES
    requests/PinChangeRequest.cpp
    requests/ImeiRequest.cpp
    requests/UssdRequest.cpp
    requests/ClipRequest.cpp
    requests/ClirRequest.cpp
    requests/ColpRequest.cpp
    requests/CallWaitingRequest.cpp
)


add_library(${PROJECT_NAME} STATIC ${SOURCES})

target_include_directories(${PROJECT_NAME}

M module-services/service-cellular/requests/CallForwardingRequest.cpp => module-services/service-cellular/requests/CallForwardingRequest.cpp +12 -5
@@ 37,18 37,21 @@ namespace cellular

    auto CallForwardingRequest::command() -> std::string
    {
        std::array<std::function<std::string()>, 2> commandParts = {
        std::array<std::function<std::string()>, 3> commandParts = {
            [this]() { return this->getCommandReason(); },
            [this]() { return this->getCommandMode(); },
            [this]() { return this->getCommandNumber(); },
        };

        std::string cmd(at::factory(at::AT::CCFC) + this->getCommandReason());
        std::string cmd(at::factory(at::AT::CCFC));
        bool formatFirst = true;
        for (auto &cmdPart : commandParts) {
            auto partStr = cmdPart();
            if (partStr.empty()) {
                break;
                continue;
            }
            cmd.append("," + partStr);
            cmd.append(formatFirst ? partStr : "," + partStr);
            formatFirst = false;
        }

        return cmd;


@@ 82,7 85,11 @@ namespace cellular

    auto CallForwardingRequest::getCommandClass() const -> std::string
    {
        return getCommandInformationClass(basicServiceGroup);
        if (basicServiceGroup.empty()) {
            return std::string();
        }

        return getCommandInformationClass(basicServiceGroup).value_or(std::string());
    }

    auto CallForwardingRequest::getCommandSubAddr() const -> std::string

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

#include <string>

#include <at/Commands.hpp>
#include <Utils.hpp>

#include "service-cellular/requests/CallWaitingRequest.hpp"

namespace cellular
{

    std::unique_ptr<SupplementaryServicesRequest> CallWaitingRequest::create(const std::string &serviceCode,
                                                                             const std::string &data,
                                                                             GroupMatch matchGroups)
    {
        if (serviceCode == callWaitingServiceCode) {
            return std::make_unique<CallWaitingRequest>(data, matchGroups);
        }
        else {
            return nullptr;
        }
    }

    auto CallWaitingRequest::command() -> std::string
    {
        if (!isValid()) {
            return std::string();
        }

        std::array<std::function<std::string()>, 3> commandParts = {
            [this]() { return this->getCommandPresentation(); },
            [this]() { return this->getCommandMode(); },
            [this]() { return this->getCommandClass(); },
        };

        std::string cmd(at::factory(at::AT::CCWA));
        bool formatFirst = true;
        for (auto &cmdPart : commandParts) {
            auto partStr = cmdPart();
            if (partStr.empty()) {
                continue;
            }
            cmd.append(formatFirst ? partStr : "," + partStr);
            formatFirst = false;
        }

        return cmd;
    }

    auto CallWaitingRequest::getCommandPresentation() const noexcept -> std::string
    {
        // fixed, we always want full report
        return unsolicitedResultCodeEnable;
    }

    auto CallWaitingRequest::getCommandMode() const noexcept -> std::string
    {
        return utils::to_string(magic_enum::enum_integer(procedureType));
    }

    auto CallWaitingRequest::getCommandClass() const noexcept -> std::string
    {
        if (basicServiceGroup.empty()) {
            return std::string();
        }

        return getCommandInformationClass(basicServiceGroup).value_or(std::string());
    }

    void CallWaitingRequest::handle(RequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }

    auto CallWaitingRequest::isValid() const noexcept -> bool
    {
        if (!getCommandInformationClass(basicServiceGroup).has_value()) {
            return false;
        }
        return procedureType != ProcedureType::Registration && procedureType != ProcedureType::Erasure;
    }

} // namespace cellular

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

#include <string>

#include <at/Commands.hpp>
#include <Utils.hpp>

#include "service-cellular/requests/ClipRequest.hpp"

namespace cellular
{

    std::unique_ptr<SupplementaryServicesRequest> ClipRequest::create(const std::string &serviceCode,
                                                                      const std::string &data,
                                                                      GroupMatch matchGroups)
    {
        if (serviceCode == clipServiceCode) {
            return std::make_unique<ClipRequest>(data, matchGroups);
        }
        else {
            return nullptr;
        }
    }

    auto ClipRequest::command() -> std::string
    {
        return isValid() ? std::string(at::factory(at::AT::CLIP_GET)) : std::string();
    }

    auto ClipRequest::isValid() const noexcept -> bool
    {
        return procedureType == ProcedureType::Interrogation;
    }

    void ClipRequest::handle(RequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }

} // namespace cellular

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

#include <string>

#include <at/Commands.hpp>
#include <Utils.hpp>

#include "service-cellular/requests/ClirRequest.hpp"

namespace cellular
{

    std::unique_ptr<SupplementaryServicesRequest> ClirRequest::create(const std::string &serviceCode,
                                                                      const std::string &data,
                                                                      GroupMatch matchGroups)
    {
        if (serviceCode == clirServiceCode) {
            return std::make_unique<ClirRequest>(data, matchGroups);
        }
        else {
            return nullptr;
        }
    }

    auto ClirRequest::command() -> std::string
    {
        switch (procedureType) {
        case ProcedureType::Deactivation:
            return std::string(at::factory(at::AT::CLIR_DISABLE));
        case ProcedureType::Activation:
            return std::string(at::factory(at::AT::CLIR_ENABLE));
        case ProcedureType::Interrogation:
            return std::string(at::factory(at::AT::CLIR_GET));
        case ProcedureType::Registration:
            // not supported
            return std::string();
        case ProcedureType::Erasure:
            // not supported
            return std::string();
        }
        return std::string();
    }

    void ClirRequest::handle(RequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }

    auto ClirRequest::isValid() const noexcept -> bool
    {
        return procedureType != ProcedureType::Registration && procedureType != ProcedureType::Erasure;
    }

} // namespace cellular

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

#include <string>

#include <at/Commands.hpp>
#include <Utils.hpp>

#include "service-cellular/requests/ColpRequest.hpp"

namespace cellular
{

    std::unique_ptr<SupplementaryServicesRequest> ColpRequest::create(const std::string &serviceCode,
                                                                      const std::string &data,
                                                                      GroupMatch matchGroups)
    {
        if (serviceCode == colpServiceCode) {
            return std::make_unique<ColpRequest>(data, matchGroups);
        }
        else {
            return nullptr;
        }
    }

    auto ColpRequest::command() -> std::string
    {
        switch (procedureType) {
        case ProcedureType::Deactivation:
            return std::string(at::factory(at::AT::COLP_DISABLE));
        case ProcedureType::Activation:
            return std::string(at::factory(at::AT::COLP_ENABLE));
        case ProcedureType::Interrogation:
            return std::string(at::factory(at::AT::COLP_GET));
        case ProcedureType::Registration:
            // not supported
            return std::string();
        case ProcedureType::Erasure:
            // not supported
            return std::string();
        }
        return std::string();
    }

    void ColpRequest::handle(RequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }

    auto ColpRequest::isValid() const noexcept -> bool
    {
        return procedureType != ProcedureType::Registration && procedureType != ProcedureType::Erasure;
    }

} // namespace cellular

M module-services/service-cellular/requests/PasswordRegistrationRequest.cpp => module-services/service-cellular/requests/PasswordRegistrationRequest.cpp +4 -0
@@ 46,6 46,10 @@ namespace cellular
            [this]() { return "," + this->getNewPassword(); },
        };

        if (!isValid()) {
            return std::string();
        }

        std::string cmd(at::factory(at::AT::CPWD));
        for (auto &cmdPart : commandParts) {
            cmd.append(cmdPart());

M module-services/service-cellular/requests/PinChangeRequest.cpp => module-services/service-cellular/requests/PinChangeRequest.cpp +4 -0
@@ 49,6 49,10 @@ namespace cellular
            [this]() { return "," + this->getNewPin(); },
        };

        if (!isValid()) {
            return std::string();
        }

        std::string cmd;
        switch (passChangeType) {
        case PassChangeType::ChangePin:

M module-services/service-cellular/requests/SupplementaryServicesRequest.cpp => module-services/service-cellular/requests/SupplementaryServicesRequest.cpp +77 -9
@@ 5,10 5,14 @@
#include <memory>

#include <at/Commands.hpp>
#include <Utils.hpp>

#include "service-cellular/requests/SupplementaryServicesRequest.hpp"
#include "service-cellular/requests/CallForwardingRequest.hpp"
#include "service-cellular/requests/PasswordRegistrationRequest.hpp"
#include "service-cellular/requests/ClipRequest.hpp"
#include "service-cellular/requests/ClirRequest.hpp"
#include "service-cellular/requests/ColpRequest.hpp"
#include "service-cellular/requests/CallWaitingRequest.hpp"

namespace
{


@@ 50,6 54,10 @@ namespace cellular

        auto factoryList = {
            CallForwardingRequest::create,
            CallWaitingRequest::create,
            ClipRequest::create,
            ClirRequest::create,
            ColpRequest::create,
        };

        for (auto &createCb : factoryList) {


@@ 62,19 70,79 @@ namespace cellular
    }

    auto SupplementaryServicesRequest::getCommandInformationClass(const std::string &basicServiceGroup) const
        -> std::string
        -> std::optional<std::string>
    {
        // according to EC25&EC21_AT_Commands_Manual_V1.3
        if (basicServiceGroup == basicServiceVoice) {
            return atInformationClassVoice;
        int basicGroup       = 0;
        int informationClass = 0;

        if (basicServiceGroup.empty()) {
            // according to 3GPP TS 22.030 V16.0.0 Annex C
            informationClass = atInformationClassAllTele + atInformationClassAllBearer;
        }
        else if (basicServiceGroup == basicServiceVoiceFax) {
            return atInformationClassFax;
        else {
            utils::toNumeric(basicServiceGroup, basicGroup);
            auto service = magic_enum::enum_cast<TeleAndBearerService>(basicGroup);

            if (!service.has_value()) {
                return std::nullopt;
            }

            switch (service.value()) {
            // according to 3GPP TS 22.030 V16.0.0 Annex C
            case TeleAndBearerService::AllTeleServices:
                informationClass = atInformationClassAllTele;
                break;
            case TeleAndBearerService::Telephony:
                informationClass = atInformationClassVoice;
                break;
            case TeleAndBearerService::AllDataTeleServices:
                informationClass = atInformationClassAllDataTele;
                break;
            case TeleAndBearerService::FacsimileServices:
                informationClass = atInformationClassFax;
                break;
            case TeleAndBearerService::ShortMessageServices:
                informationClass = atInformationClassSms;
                break;
            case TeleAndBearerService::AllTeleServicesExceptSms:
                informationClass = atInformationClassAllTele - atInformationClassSms;
                break;
            case TeleAndBearerService::VoiceGroupCallService:
                LOG_INFO("Unsupported information class: 17 - Voice Group Call Service");
                break;
            case TeleAndBearerService::VoiceBroadcastService:
                LOG_INFO("Unsupported information class: 18 - Voice Broadcast Service");
                break;
            case TeleAndBearerService::AllBearerServices:
                informationClass = atInformationClassAllBearer;
                break;
            case TeleAndBearerService::AllAsyncServices:
                informationClass = atInformationClassDataAsync;
                break;
            case TeleAndBearerService::AllSyncServices:
                informationClass = atInformationClassDataSync;
                break;
            case TeleAndBearerService::AllDataCircuitSync:
                informationClass = atInformationClassDataSync;
                break;
            case TeleAndBearerService::AllDataCircuitAsync:
                informationClass = atInformationClassDataAsync;
                break;
            case TeleAndBearerService::AllGprsBearerServices:
                LOG_INFO("Unsupported information class: 99 - All Gprs Bearer Services");
                break;
            case TeleAndBearerService::TelephonyAndAllSyncServices:
                informationClass = atInformationClassVoice + atInformationClassDataSync;
                break;
            }
        }
        else if (basicServiceGroup == basicServiceVoiceData) {
            return atInformationClassData;

        if (informationClass == 0) {
            return std::nullopt;
        }
        return atInformationClassAllButSms;

        return utils::to_string(informationClass);
    }

}; // namespace cellular

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

#pragma once

#include "SupplementaryServicesRequest.hpp"

namespace cellular
{
    class CallWaitingRequest : public SupplementaryServicesRequest
    {
      public:
        CallWaitingRequest(const std::string &data, GroupMatch matchGroups)
            : SupplementaryServicesRequest(data, matchGroups)
        {}

        static std::unique_ptr<SupplementaryServicesRequest> create(const std::string &serviceCode,
                                                                    const std::string &data,
                                                                    GroupMatch matchGroups);

        auto command() -> std::string final;
        void handle(RequestHandler &h, at::Result &result) final;
        auto isValid() const noexcept -> bool final;

        auto getCommandPresentation() const noexcept -> std::string;
        auto getCommandMode() const noexcept -> std::string;
        auto getCommandClass() const noexcept -> std::string;

      private:
        static constexpr auto callWaitingServiceCode      = "43";
        static constexpr auto unsolicitedResultCodeEnable = "1";

        std::string &basicServiceGroup = supplementaryInfoA;
    };
}; // namespace cellular

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

#pragma once

#include "SupplementaryServicesRequest.hpp"

namespace cellular
{
    class ClipRequest : public SupplementaryServicesRequest
    {
      public:
        ClipRequest(const std::string &data, GroupMatch matchGroups) : SupplementaryServicesRequest(data, matchGroups)
        {}

        static std::unique_ptr<SupplementaryServicesRequest> create(const std::string &serviceCode,
                                                                    const std::string &data,
                                                                    GroupMatch matchGroups);

        auto command() -> std::string final;
        void handle(RequestHandler &h, at::Result &result) final;
        auto isValid() const noexcept -> bool final;

      private:
        static constexpr auto clipServiceCode = "30";
    };
}; // namespace cellular

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

#pragma once

#include "SupplementaryServicesRequest.hpp"

namespace cellular
{
    class ClirRequest : public SupplementaryServicesRequest
    {
      public:
        ClirRequest(const std::string &data, GroupMatch matchGroups) : SupplementaryServicesRequest(data, matchGroups)
        {}

        static std::unique_ptr<SupplementaryServicesRequest> create(const std::string &serviceCode,
                                                                    const std::string &data,
                                                                    GroupMatch matchGroups);

        auto command() -> std::string final;
        void handle(RequestHandler &h, at::Result &result) final;
        auto isValid() const noexcept -> bool final;

      private:
        static constexpr auto clirServiceCode = "31";
    };
}; // namespace cellular

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

#pragma once

#include "SupplementaryServicesRequest.hpp"

namespace cellular
{
    class ColpRequest : public SupplementaryServicesRequest
    {
      public:
        ColpRequest(const std::string &data, GroupMatch matchGroups) : SupplementaryServicesRequest(data, matchGroups)
        {}

        static std::unique_ptr<SupplementaryServicesRequest> create(const std::string &serviceCode,
                                                                    const std::string &data,
                                                                    GroupMatch matchGroups);

        auto command() -> std::string final;
        void handle(RequestHandler &h, at::Result &result) final;
        auto isValid() const noexcept -> bool final;

      private:
        static constexpr auto colpServiceCode = "76";
    };
}; // namespace cellular

M module-services/service-cellular/service-cellular/requests/SupplementaryServicesRequest.hpp => module-services/service-cellular/service-cellular/requests/SupplementaryServicesRequest.hpp +41 -13
@@ 34,13 34,14 @@ namespace cellular
        static std::unique_ptr<SupplementaryServicesRequest> create(const std::string &data, GroupMatch matchGroups);
        SupplementaryServicesRequest(const std::string &data, GroupMatch matchGroups);

        // according to 3GPP TS 22.030 V16.0.0
        enum class ProcedureType
        {
            Deactivation,
            Activation,
            Interrogation,
            Registration,
            Erasure
            Deactivation,  ///< MMI string starts with #
            Activation,    ///< MMI string starts with *
            Interrogation, ///< MMI string starts with *#
            Registration,  ///< MMI string starts with **
            Erasure        ///< MMI string starts with ##
        };

      protected:


@@ 51,15 52,42 @@ namespace cellular
        std::string supplementaryInfoC;
        ProcedureType procedureType;

        static constexpr inline auto atInformationClassVoice     = "1";
        static constexpr inline auto atInformationClassData      = "2";
        static constexpr inline auto atInformationClassFax       = "4";
        static constexpr inline auto atInformationClassAllButSms = "7";
        // according to EC25&EC21_AT_Commands_Manual_V1.3
        static constexpr inline auto atInformationClassVoice     = 1 << 0;
        static constexpr inline auto atInformationClassData      = 1 << 1;
        static constexpr inline auto atInformationClassFax       = 1 << 2;
        static constexpr inline auto atInformationClassSms       = 1 << 3;
        static constexpr inline auto atInformationClassDataSync  = 1 << 4;
        static constexpr inline auto atInformationClassDataAsync = 1 << 5;

        static constexpr inline auto basicServiceVoice     = "11";
        static constexpr inline auto basicServiceVoiceData = "25";
        static constexpr inline auto basicServiceVoiceFax  = "13";
        static constexpr inline auto atInformationClassAllTele =
            atInformationClassVoice + atInformationClassFax + atInformationClassSms;
        static constexpr inline auto atInformationClassAllDataTele = atInformationClassFax + atInformationClassSms;
        static constexpr inline auto atInformationClassAllBearer =
            atInformationClassDataSync + atInformationClassDataAsync + atInformationClassData;

        auto getCommandInformationClass(const std::string &basicServiceGroup) const -> std::string;
        // according to 3GPP TS 22.030 V16.0.0
        enum class TeleAndBearerService
        {
            AllTeleServices          = 10,
            Telephony                = 11,
            AllDataTeleServices      = 12,
            FacsimileServices        = 13,
            ShortMessageServices     = 16,
            AllTeleServicesExceptSms = 19,

            VoiceGroupCallService = 17,
            VoiceBroadcastService = 18,

            AllBearerServices           = 20,
            AllAsyncServices            = 21,
            AllSyncServices             = 22,
            AllDataCircuitSync          = 24,
            AllDataCircuitAsync         = 25,
            AllGprsBearerServices       = 99,
            TelephonyAndAllSyncServices = 26,
        };

        auto getCommandInformationClass(const std::string &basicServiceGroup) const -> std::optional<std::string>;
    };
}; // namespace cellular

M module-services/service-cellular/tests/unittest_mmi.cpp => module-services/service-cellular/tests/unittest_mmi.cpp +85 -2
@@ 6,10 6,14 @@
#include <catch2/catch.hpp>
#include <service-cellular/RequestFactory.hpp>
#include <service-cellular/requests/CallForwardingRequest.hpp>
#include <service-cellular/requests/CallWaitingRequest.hpp>
#include <service-cellular/requests/PasswordRegistrationRequest.hpp>
#include <service-cellular/requests/PinChangeRequest.hpp>
#include <service-cellular/requests/ImeiRequest.hpp>
#include <service-cellular/requests/UssdRequest.hpp>
#include <service-cellular/requests/ClipRequest.hpp>
#include <service-cellular/requests/ClirRequest.hpp>
#include <service-cellular/requests/ColpRequest.hpp>

using namespace cellular;



@@ 24,9 28,85 @@ TEST_CASE("MMI requests")
    };

    std::vector<TestCase> testCases = {
        /// IMEIRequest
        /// ImeiRequest
        {R"(*#06#)", R"(AT+GSN)", typeid(ImeiRequest)},

        /// ClipRequest
        // bad procedure type
        {R"(*30#)", std::string(), typeid(ClipRequest), false},
        // query
        {R"(*#30#)", R"(AT+CLIP?)", typeid(ClipRequest)},
        // bad procedure type
        {R"(##30#)", std::string(), typeid(ClipRequest), false},
        // bad procedure type
        {R"(#30#)", std::string(), typeid(ClipRequest), false},
        // bad procedure type
        {R"(**30#)", std::string(), typeid(ClipRequest), false},
        // temporary mode enable
        {R"(*30#600700800)", std::string(), typeid(CallRequest)},
        // temporary mode disable
        {R"(#30#600700800)", std::string(), typeid(CallRequest)},

        /// ClirRequest
        // bad procedure type
        {R"(*31#)", R"(AT+CLIR=1)", typeid(ClirRequest)},
        // query
        {R"(*#31#)", R"(AT+CLIR?)", typeid(ClirRequest)},
        // bad procedure type
        {R"(##31#)", std::string(), typeid(ClirRequest), false},
        // bad procedure type
        {R"(#31#)", R"(AT+CLIR=2)", typeid(ClirRequest)},
        // bad procedure type
        {R"(**31#)", std::string(), typeid(ClirRequest), false},

        /// ColpRequest
        // bad procedure type
        {R"(*76#)", R"(AT+COLP=1)", typeid(ColpRequest)},
        // query
        {R"(*#76#)", R"(AT+COLP?)", typeid(ColpRequest)},
        // bad procedure type
        {R"(##76#)", std::string(), typeid(ColpRequest), false},
        // bad procedure type
        {R"(#76#)", R"(AT+COLP=0)", typeid(ColpRequest)},
        // bad procedure type
        {R"(**76#)", std::string(), typeid(ColpRequest), false},

        /// CallWaitingRequest
        // enable all
        {R"(*43#)", R"(AT+CCWA=1,1)", typeid(CallWaitingRequest)},
        // enable all
        {R"(*43*#)", R"(AT+CCWA=1,1)", typeid(CallWaitingRequest)},
        // enable all tele services
        {R"(*43*10#)", R"(AT+CCWA=1,1,13)", typeid(CallWaitingRequest)},
        // enable voice
        {R"(*43*11#)", R"(AT+CCWA=1,1,1)", typeid(CallWaitingRequest)},
        // enable data
        {R"(*43*12#)", R"(AT+CCWA=1,1,12)", typeid(CallWaitingRequest)},
        // enable fax
        {R"(*43*13#)", R"(AT+CCWA=1,1,4)", typeid(CallWaitingRequest)},
        // enable sms
        {R"(*43*16#)", R"(AT+CCWA=1,1,8)", typeid(CallWaitingRequest)},
        // enable all tele except sms
        {R"(*43*19#)", R"(AT+CCWA=1,1,5)", typeid(CallWaitingRequest)},
        // enable all bearer
        {R"(*43*20#)", R"(AT+CCWA=1,1,50)", typeid(CallWaitingRequest)},
        // enable data circuit sync
        {R"(*43*24#)", R"(AT+CCWA=1,1,16)", typeid(CallWaitingRequest)},
        // enable data circuit async
        {R"(*43*25#)", R"(AT+CCWA=1,1,32)", typeid(CallWaitingRequest)},
        // disable all
        {R"(#43#)", R"(AT+CCWA=1,0)", typeid(CallWaitingRequest)},
        // query all
        {R"(*#43#)", R"(AT+CCWA=1,2)", typeid(CallWaitingRequest)},
        // bad procedure **
        {R"(**43#)", std::string(), typeid(CallWaitingRequest), false},
        // bad procedure ##
        {R"(##43#)", std::string(), typeid(CallWaitingRequest), false},
        // bad service group
        {R"(*43*17#)", std::string(), typeid(CallWaitingRequest), false},
        // bad service group
        {R"(*43*17#)", std::string(), typeid(CallWaitingRequest), false},

        /// CallForwardingRequest
        // all diversions
        {R"(##002#)", R"(AT+CCFC=4,4)", typeid(CallForwardingRequest)},


@@ 134,8 214,11 @@ TEST_CASE("MMI requests")
        INFO("Failed on testcase: " << testCase.requestString);
        REQUIRE(typeid(*request.get()).name() == testCase.expectedType.name());
        REQUIRE(request->isValid() == testCase.expectedValid);
        if (typeid(*request.get()).name() != typeid(CallRequest).name() && request->isValid()) {
        if (typeid(*request.get()).name() != typeid(CallRequest).name()) {
            REQUIRE(requestCommand == testCase.expectedCommandString);
        }
        else {
            REQUIRE(requestCommand == "ATD" + testCase.requestString + ";");
        }
    }
}