~aleteoryx/muditaos

6a92a7261ef1b6cab41c4b197eae4e79ea893721 — Hubert Chrzaniuk 5 years ago ff7af96
[EGD-2962] Refactor / improve URC handling (#947)

 - QIND URC has been extended to support FOTA reply
 - added support for CLIP, CMTI and POWERED DOWN URCs
 - added support for URC responses codes handling
 - created URCHandler abstraction and moved out URC handling from
   cellular service code
 - URC objects are now created by factory on a basis of URC head, rather
   than previous approach where they were created a priori
 - Renamed classes to be consistent with rest of the project
38 files changed, 1636 insertions(+), 579 deletions(-)

M changelog.md
M module-cellular/CMakeLists.txt
M module-cellular/Modem/TS0710/TS0710.cpp
D module-cellular/at/URC_Any.hpp
A module-cellular/at/Urc.hpp
A module-cellular/at/UrcClip.hpp
A module-cellular/at/UrcCmti.hpp
R module-cellular/at/{URC_CREG => UrcCreg}.hpp
R module-cellular/at/{URC_CTZE => UrcCtze}.hpp
R module-cellular/at/{URC_CUSD => UrcCusd}.hpp
A module-cellular/at/UrcFactory.hpp
A module-cellular/at/UrcHandler.hpp
A module-cellular/at/UrcPoweredDown.hpp
R module-cellular/at/{URC_QIND => UrcQind}.hpp
A module-cellular/at/UrcResponse.hpp
M module-cellular/at/response.cpp
D module-cellular/at/src/URC_Any.cpp
A module-cellular/at/src/Urc.cpp
A module-cellular/at/src/UrcClip.cpp
A module-cellular/at/src/UrcCmti.cpp
R module-cellular/at/src/{URC_CREG => UrcCreg}.cpp
R module-cellular/at/src/{URC_CTZE => UrcCtze}.cpp
R module-cellular/at/src/{URC_CUSD => UrcCusd}.cpp
A module-cellular/at/src/UrcFactory.cpp
A module-cellular/at/src/UrcPoweredDown.cpp
R module-cellular/at/src/{URC_QIND => UrcQind}.cpp
A module-cellular/at/src/UrcResponse.cpp
M module-cellular/test/unittest_URC.cpp
M module-services/service-cellular/CMakeLists.txt
A module-services/service-cellular/CellularUrcHandler.cpp
A module-services/service-cellular/CellularUrcHandler.hpp
M module-services/service-cellular/ServiceCellular.cpp
M module-services/service-cellular/ServiceCellular.hpp
M module-services/service-fota/CMakeLists.txt
A module-services/service-fota/FotaUrcHandler.cpp
A module-services/service-fota/FotaUrcHandler.hpp
M module-services/service-fota/ServiceFota.cpp
M module-services/service-fota/ServiceFota.hpp
M changelog.md => changelog.md +1 -0
@@ 49,6 49,7 @@
* `[doc]` Application manager documentation added.
* `[audio]` Improve synchronization during calls.
* `[system]` Application manager refactoring and improvements.
* `[cellular]` URC handling refactor.

## [0.43.1 2020-10-23]


M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +10 -5
@@ 23,11 23,16 @@ set(SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/Modem/TS0710/TS0710_START.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/Modem/TS0710/TS0710_TEST.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/Modem/TS0710/TS0710_WAKEUP.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/URC_Any.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/URC_QIND.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/URC_CUSD.cpp       
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/URC_CTZE.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/URC_CREG.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/Urc.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcQind.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcCusd.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcCtze.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcCreg.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcCmti.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcClip.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
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/Commands.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/response.cpp
        )

M module-cellular/Modem/TS0710/TS0710.cpp => module-cellular/Modem/TS0710/TS0710.cpp +0 -1
@@ 8,7 8,6 @@
#include "service-cellular/SignalStrength.hpp"
#include "service-cellular/messages/CellularMessage.hpp"
#include <Service/Bus.hpp>
#include <at/URC_QIND.hpp>
#include <cassert>
#include <memory>
#include <module-os/RTOSWrapper/include/ticks.hpp>

D module-cellular/at/URC_Any.hpp => module-cellular/at/URC_Any.hpp +0 -36
@@ 1,36 0,0 @@
// 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 "Utils.hpp"
#include <log/log.hpp>
#include <string>
#include <vector>

namespace at::urc
{

    /// generally URC are like that: re'*([+^][A-Z]): (([A-Z],)+)' where first group is `head` and rest is tokens
    /// i.e. in:
    /// +QIND: "csq",15,99
    /// '+QIND' - head
    /// '"csq", 15, 99' are three tokens
    class Any
    {
      public:
        Any(const std::string &str, char tokenDelimiter = ',');
        virtual ~Any()                     = default;
        virtual auto what() const noexcept -> std::string = 0;
        /// check if urc parsed is of proper type - i.e. QIND
        virtual auto is() const -> bool final;

        auto getTokens() const -> std::vector<std::string>;

      protected:
        std::string head;
        std::vector<std::string> tokens;
        /// split urc into: head and tokenized data (as in class descripiton)
        virtual void split(const std::string &str, char tokenDelimiter = ',') final;
    };
}; // namespace at::urc

A module-cellular/at/Urc.hpp => module-cellular/at/Urc.hpp +88 -0
@@ 0,0 1,88 @@
// 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 <module-utils/Utils.hpp>
#include "UrcHandler.hpp"

namespace at::urc

{
    class Urc
    {
      public:
        /**
         * Parses Urc body and constructs an instance.
         * @param urcBody - Urc message body without the header
         * @param urcHead - Urc message head
         * @param tokenDelimiter - sign that separates parameters in Urc message
         */
        Urc(const std::string &urcBody, const std::string &urcHead = std::string(), char tokenDelimiter = ',');

        virtual ~Urc() = default;

        /**
         * Checks weather Urc message is valid. Should be overridden in derived class of concrete Urc.
         * @return true if valid, false otherwise
         */
        virtual auto isValid() const noexcept -> bool
        {
            return !urcBody.empty() || !urcHead.empty();
        }

        /**
         * Gets vector of strings that represent Urc parameters.
         * @return vector or parameters in order of appearance in message
         */
        auto getTokens() const -> std::vector<std::string>;

        /**
         * Gets Urc body stripped of urc header.
         * @return Urc body string
         */
        auto getUrcBody() const -> std::string
        {
            return urcBody;
        }

        /**
         * Call for dispatching handling to UrcHandler
         * @param h - implementation of UrcHandler
         */
        virtual void Handle(UrcHandler &h)
        {}

        /**
         * Flag Urc handled/unhandled
         * @param state - true for handled, false for unhandled
         */
        void setHandled(bool state)
        {
            isUrcHandled = state;
        }

        /**
         * @return true if Urc has been flagged as handled, false otherwise
         */
        bool isHandled()
        {
            return isUrcHandled;
        }

      protected:
        std::vector<std::string> tokens;
        std::string urcBody;
        std::string urcHead;

        bool isUrcHandled = false;

        /**
         * Splits Urc into head and tokenized data, cleans tokens from whitespaces and quotes
         * @param str - string to be split
         * @param tokenDelimiter - sign that separates parameters in Urc message
         */
        void split(const std::string &str, char tokenDelimiter = ',');
    };

} // namespace at::urc

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

#pragma once

#include "Urc.hpp"
#include "UrcHandler.hpp"

namespace at::urc
{
    class Clip : public Urc
    {
        enum class Tokens
        {
            Number,
            Type,
            Subaddr,
            Satype,
            Alpha,
            CLIValidity,
        };

        const size_t minParametersCount = magic_enum::enum_integer(Tokens::Type) + 1;

      public:
        enum class AddressType
        {
            UnknownType         = 129,
            InternationalNumber = 145,
            NationalNumber      = 161,
        };

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

        using Urc::Urc;

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

        [[nodiscard]] auto getNumber() const -> std::string;
        [[nodiscard]] auto getType() const -> std::optional<Clip::AddressType>;
        [[nodiscard]] auto getSubaddr() const -> std::optional<std::string>;
        [[nodiscard]] auto getSatype() const -> std::optional<std::string>;
        [[nodiscard]] auto getAlpha() const -> std::optional<std::string>;
        [[nodiscard]] auto getCLIValidity() const -> std::optional<std::string>;

        void Handle(UrcHandler &h) final
        {
            h.Handle(*this);
        }
    };
} // namespace at::urc

A module-cellular/at/UrcCmti.hpp => module-cellular/at/UrcCmti.hpp +37 -0
@@ 0,0 1,37 @@
// 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 "Urc.hpp"

namespace at::urc
{
    class Cmti : public Urc
    {
        enum Tokens
        {
            Mem,
            Index
        };

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

        using Urc::Urc;

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

        [[nodiscard]] auto getMemory() const -> std::string;
        [[nodiscard]] auto getIndex() const -> std::string;

        void Handle(UrcHandler &h) final
        {
            h.Handle(*this);
        }
    };
} // namespace at::urc

R module-cellular/at/URC_CREG.hpp => module-cellular/at/UrcCreg.hpp +15 -8
@@ 3,7 3,7 @@

#pragma once

#include "URC_Any.hpp"
#include "Urc.hpp"

#include <common_data/EventStore.hpp>



@@ 11,9 11,8 @@ namespace at::urc
{
    /// +CREG: <stat> - Indicate network registration status of the ME
    /// +CREG: <stat>[,<lac>,<ci>[,<Act>]] - Indicate network registration and location information of the ME
    class CREG : public Any
    class Creg : public Urc
    {
        const std::string urc_name = "+CREG";
        enum Tokens
        {
            Stat,


@@ 24,12 23,15 @@ namespace at::urc
        };

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

        [[nodiscard]] auto what() const noexcept -> std::string final;
        using Urc::Urc;

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



@@ 37,5 39,10 @@ namespace at::urc
        [[nodiscard]] auto getLocation() const noexcept -> std::optional<std::string>;
        [[nodiscard]] auto getCellId() const noexcept -> std::optional<std::string>;
        [[nodiscard]] auto getAccessTechnology() const noexcept -> Store::Network::AccessTechnology;

        void Handle(UrcHandler &h) final
        {
            h.Handle(*this);
        }
    };
}; // namespace at::urc
} // namespace at::urc

R module-cellular/at/URC_CTZE.hpp => module-cellular/at/UrcCtze.hpp +20 -12
@@ 3,14 3,13 @@

#pragma once

#include "URC_Any.hpp"
#include "Urc.hpp"
#include <ctime>

namespace at::urc
{
    class CTZE : public Any
    class Ctze : public Urc
    {
        const std::string urc_name = "+CTZE";
        enum Tokens
        {
            GMTDifference,


@@ 20,18 19,27 @@ namespace at::urc
        };

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

        using Urc::Urc;

        constexpr static int maxTimezoneOffset = 56;
        constexpr static int minTimezoneOffset = -48;

        ~CTZE() override = default;
        [[nodiscard]] auto what() const noexcept -> std::string final;
        [[nodiscard]] auto isValid() const noexcept -> bool;
        [[nodiscard]] auto getTimeInfo(void) const noexcept -> tm;
        [[nodiscard]] auto isValid() const noexcept -> bool override;
        [[nodiscard]] auto getTimeInfo() const noexcept -> tm;

        auto getTimeZoneOffset() const -> int;
        auto getTimeZoneString() const -> std::string;
        auto getGMTTime(void) const -> const struct tm;
        [[nodiscard]] auto getTimeZoneOffset() const -> int;
        [[nodiscard]] auto getTimeZoneString() const -> std::string;
        [[nodiscard]] auto getGMTTime() const -> const struct tm;

        void Handle(UrcHandler &h) final
        {
            h.Handle(*this);
        }
    };
}; // namespace at::urc
} // namespace at::urc

R module-cellular/at/URC_CUSD.hpp => module-cellular/at/UrcCusd.hpp +16 -7
@@ 3,17 3,16 @@

#pragma once

#include "URC_Any.hpp"
#include "Urc.hpp"

#include <optional>

namespace at::urc
{

    class CUSD : public Any
    class Cusd : public Urc
    {

        const std::string urc_name = "+CUSD";
        enum Tokens
        {
            Status,


@@ 32,14 31,24 @@ namespace at::urc
            NetworkTimeOut
        };

        explicit CUSD(const std::string &val);
        ~CUSD() override = default;
        static constexpr std::string_view head = "+CUSD";
        static auto isURC(const std::string uHead) -> bool
        {
            return uHead.find(Cusd::head) != std::string::npos;
        }

        using Urc::Urc;

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

        [[nodiscard]] auto what() const noexcept -> std::string final;
        [[nodiscard]] auto isValid() const noexcept -> bool;
        [[nodiscard]] auto isActionNeeded() const noexcept -> bool;
        [[nodiscard]] auto getMessage() const noexcept -> std::optional<std::string>;
        [[nodiscard]] auto getStatus() const noexcept -> std::optional<StatusType>;
        [[nodiscard]] auto getDCS() const noexcept -> std::optional<int>;

        void Handle(UrcHandler &h) final
        {
            h.Handle(*this);
        }
    };
} // namespace at::urc

A module-cellular/at/UrcFactory.hpp => module-cellular/at/UrcFactory.hpp +20 -0
@@ 0,0 1,20 @@
// 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 "Urc.hpp"

namespace at::urc
{
    class UrcFactory
    {
      public:
        /**
         * Instantiates concrete Urc class on a base of Urc message body.
         * @param urcMessage - Urc message to be parsed
         * @return pointer to Urc
         */
        static std::unique_ptr<Urc> Create(const std::string &urcMessage);
    };
} // namespace at::urc

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

#pragma once

namespace at::urc
{
    class Clip;
    class Creg;
    class Cmti;
    class Cusd;
    class Ctze;
    class Qind;
    class PoweredDown;
    class UrcResponse;

    class UrcHandler
    {
      public:
        virtual void Handle(Clip &urc)        = 0;
        virtual void Handle(Creg &urc)        = 0;
        virtual void Handle(Cmti &urc)        = 0;
        virtual void Handle(Cusd &urc)        = 0;
        virtual void Handle(Ctze &urc)        = 0;
        virtual void Handle(Qind &urc)        = 0;
        virtual void Handle(PoweredDown &urc) = 0;
        virtual void Handle(UrcResponse &urc) = 0;
    };
} // namespace at::urc

A module-cellular/at/UrcPoweredDown.hpp => module-cellular/at/UrcPoweredDown.hpp +36 -0
@@ 0,0 1,36 @@
// 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 "Urc.hpp"

namespace at::urc
{
    class PoweredDown : public 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
        {
            auto isImmediatePowerDown = uHead.find(PoweredDown::head_immediate) != std::string::npos;
            auto isNormalPowerDown    = uHead.find(PoweredDown::head_normal) != std::string::npos;
            return isImmediatePowerDown || isNormalPowerDown;
        }

        using Urc::Urc;

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

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

      private:
        bool isNormal = false;
    };
} // namespace at::urc

R module-cellular/at/URC_QIND.hpp => module-cellular/at/UrcQind.hpp +41 -9
@@ 3,14 3,15 @@

#pragma once

#include "URC_Any.hpp"
#include "Urc.hpp"

namespace at::urc
{
    class QIND : public Any
    class Qind : public Urc
    {
        const std::string urc_name = "+QIND";
        const std::string type_csq = "\"csq\"";

        const std::string type_csq  = "csq";
        const std::string type_fota = "FOTA";

        static const auto invalid_rssi_low  = 99;
        static const auto invalid_rssi_high = 199;


@@ 23,17 24,48 @@ namespace at::urc
            BER
        };

        enum FOTA
        {
            Fota,
            Stage,
            Param
        };

        const size_t fotaMinTokenSize = 2;

        /// by docs invalid csq: RSSI: 99, 199, and ber: 99
        [[nodiscard]] auto validate(enum CSQ) const noexcept -> bool;

      public:
        explicit QIND(const std::string &val);
        ~QIND() override = default;
        [[nodiscard]] auto what() const noexcept -> std::string final;
        enum class FotaStage
        {
            HTTPSTART,
            HTTPEND,
            START,
            UPDATING,
            END,
        };

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

        using Urc::Urc;

        [[nodiscard]] auto isCsq() const noexcept -> bool;
        [[nodiscard]] auto getRSSI() const noexcept -> std::optional<int>;
        [[nodiscard]] auto getBER() const noexcept -> std::optional<int>;

        [[nodiscard]] auto isFota() const noexcept -> bool;
        [[nodiscard]] auto isFotaValid() const noexcept -> bool;
        [[nodiscard]] auto getFotaStage() const noexcept -> std::optional<FotaStage>;
        [[nodiscard]] auto getFotaParameter() const noexcept -> std::string;

        void Handle(UrcHandler &h) final
        {
            h.Handle(*this);
        }
    };
}; // namespace at::urc
} // namespace at::urc

A module-cellular/at/UrcResponse.hpp => module-cellular/at/UrcResponse.hpp +59 -0
@@ 0,0 1,59 @@
// 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 "Urc.hpp"

namespace at::urc
{
    class UrcResponse : public Urc
    {
      public:
        // this table has to be aligned with responseBodies
        enum class URCResponseType
        {
            Ok,
            Connect,
            Ring,
            NoCarrier,
            Error,
            NoDialtone,
            Busy,
            NoAnswer
        };

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

        explicit UrcResponse(URCResponseType type) : Urc(std::string()), type(type){};

        auto getURCResponseType() const noexcept -> URCResponseType;

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

      private:
        URCResponseType type;

        static constexpr std::array<std::pair<URCResponseType, std::string_view>, 8> urcResponses = {{
            {URCResponseType::Ok, "OK"},
            {URCResponseType::Connect, "CONNECT"},
            {URCResponseType::Ring, "RING"},
            {URCResponseType::NoCarrier, "NO CARRIER"},
            {URCResponseType::Error, "ERROR"},
            {URCResponseType::NoDialtone, "NO DIALTONE"},
            {URCResponseType::Busy, "BUSY"},
            {URCResponseType::NoAnswer, "NO ANSWER"},
        }};
    };
} // namespace at::urc

M module-cellular/at/response.cpp => module-cellular/at/response.cpp +1 -1
@@ 45,7 45,7 @@ namespace at
            bool isRegistered(uint32_t commandData)
            {

                // CREG command returns 1 when registered in home network, 5 when registered in roaming
                // Creg command returns 1 when registered in home network, 5 when registered in roaming
                constexpr uint32_t registeredHome    = 1;
                constexpr uint32_t registeredRoaming = 5;


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

#include <URC_Any.hpp>

namespace at::urc
{

    Any::Any(const std::string &str, char tokenDelimiter)
    {
        split(str, tokenDelimiter);
    }

    void Any::split(const std::string &str, char tokenDelimiter)
    {
        const std::string delim = ":";
        auto pos                = str.find(delim);
        head                    = std::string(str, 0, pos);
        if (pos != std::string::npos) {
            tokens = utils::split(std::string(str.begin() + pos + delim.size(), str.end()), tokenDelimiter);
        } // else - everyting went to head
    }

    auto Any::is() const -> bool
    {
        return head.find(what()) != std::string::npos;
    }

    auto Any::getTokens() const -> std::vector<std::string>
    {
        return tokens;
    }

} // namespace at::urc

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

#include <Urc.hpp>
#include <algorithm>

namespace at::urc
{
    Urc::Urc(const std::string &urcBody, const std::string &urcHead, char tokenDelimiter)
        : urcBody(urcBody), urcHead(urcHead)
    {
        split(urcBody, tokenDelimiter);
    }

    void Urc::split(const std::string &str, char tokenDelimiter)
    {
        tokens                            = utils::split(str, tokenDelimiter);
        constexpr auto urcStringDelimiter = "\"";
        for (auto &t : tokens) {
            utils::findAndReplaceAll(t, urcStringDelimiter, "");
            t = utils::trim(t);
        }
    }

    auto Urc::getTokens() const -> std::vector<std::string>
    {
        return tokens;
    }
} // namespace at::urc

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

#include "UrcClip.hpp"

using namespace at::urc;

auto Clip::isValid() const noexcept -> bool
{
    return tokens.size() >= minParametersCount;
}

std::string Clip::getNumber() const
{
    if (!isValid()) {
        return std::string();
    }
    return tokens[magic_enum::enum_integer(Tokens::Number)];
};

std::optional<Clip::AddressType> Clip::getType() const
{
    if (!isValid()) {
        return std::nullopt;
    }

    int addressType;
    if (!utils::toNumeric(tokens[magic_enum::enum_integer(Tokens::Type)], addressType)) {
        return std::nullopt;
    }

    constexpr auto addressTypes = magic_enum::enum_values<Clip::AddressType>();
    for (const auto &type : addressTypes) {
        if (magic_enum::enum_integer(type) == addressType) {
            return type;
        }
    }

    return std::nullopt;
};

std::optional<std::string> Clip::getSubaddr() const
{
    size_t minNumOfParams = magic_enum::enum_integer(Tokens::Subaddr) + 1;
    if (tokens.size() < minNumOfParams) {
        return std::nullopt;
    }
    if (tokens[magic_enum::enum_integer(Tokens::Subaddr)].empty()) {
        return std::nullopt;
    }
    return tokens[magic_enum::enum_integer(Tokens::Subaddr)];
};

std::optional<std::string> Clip::getSatype() const
{
    size_t minNumOfParams = magic_enum::enum_integer(Tokens::Satype) + 1;
    if (tokens.size() < minNumOfParams) {
        return std::nullopt;
    }
    if (tokens[magic_enum::enum_integer(Tokens::Satype)].empty()) {
        return std::nullopt;
    }
    return tokens[magic_enum::enum_integer(Tokens::Satype)];
};

std::optional<std::string> Clip::getAlpha() const
{
    size_t minNumOfParams = magic_enum::enum_integer(Tokens::Alpha) + 1;
    if (tokens.size() < minNumOfParams) {
        return std::nullopt;
    }
    if (tokens[magic_enum::enum_integer(Tokens::Alpha)].empty()) {
        return std::nullopt;
    }
    return tokens[magic_enum::enum_integer(Tokens::Alpha)];
};

std::optional<std::string> Clip::getCLIValidity() const
{
    size_t minNumOfParams = magic_enum::enum_integer(Tokens::CLIValidity) + 1;
    if (tokens.size() < minNumOfParams) {
        return std::nullopt;
    }
    if (tokens[magic_enum::enum_integer(Tokens::CLIValidity)].empty()) {
        return std::nullopt;
    }
    return tokens[magic_enum::enum_integer(Tokens::CLIValidity)];
};

A module-cellular/at/src/UrcCmti.cpp => module-cellular/at/src/UrcCmti.cpp +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

#include "UrcCmti.hpp"

using namespace at::urc;

auto Cmti::isValid() const noexcept -> bool
{
    return tokens.size() == magic_enum::enum_count<Tokens>();
}

std::string Cmti::getMemory() const
{
    if (!isValid()) {
        return std::string();
    }
    return tokens[Tokens::Mem];
}

std::string Cmti::getIndex() const
{
    if (!isValid()) {
        return std::string();
    }
    return tokens[Tokens::Index];
}

R module-cellular/at/src/URC_CREG.cpp => module-cellular/at/src/UrcCreg.cpp +9 -17
@@ 1,35 1,27 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <URC_CREG.hpp>
#include <UrcCreg.hpp>
#include <magic_enum.hpp>

using namespace at::urc;

CREG::CREG(const std::string &val) : Any(val)
{}

auto CREG::what() const noexcept -> std::string
{
    return urc_name;
}

auto CREG::isValid() const noexcept -> bool
auto Creg::isValid() const noexcept -> bool
{
    return is() && (isExtended() || isShort());
    return isExtended() || isShort();
}

auto CREG::isExtended() const noexcept -> bool
auto Creg::isExtended() const noexcept -> bool
{
    return tokens.size() == magic_enum::enum_count<Tokens>();
}

auto CREG::isShort() const noexcept -> bool
auto Creg::isShort() const noexcept -> bool
{
    return tokens.size() == NumOfShortTokens;
}

auto CREG::getStatus() const noexcept -> Store::Network::Status
auto Creg::getStatus() const noexcept -> Store::Network::Status
{
    if (isValid()) {
        int statusInt;


@@ 48,7 40,7 @@ auto CREG::getStatus() const noexcept -> Store::Network::Status
    return Store::Network::Status::Unknown;
}

auto CREG::getLocation() const noexcept -> std::optional<std::string>
auto Creg::getLocation() const noexcept -> std::optional<std::string>
{
    if (isValid() && isExtended()) {
        auto location = tokens[Tokens::Lac];


@@ 59,7 51,7 @@ auto CREG::getLocation() const noexcept -> std::optional<std::string>
    return std::nullopt;
}

auto CREG::getCellId() const noexcept -> std::optional<std::string>
auto Creg::getCellId() const noexcept -> std::optional<std::string>
{
    if (isValid() && isExtended()) {
        auto cellId = tokens[Tokens::Ci];


@@ 70,7 62,7 @@ auto CREG::getCellId() const noexcept -> std::optional<std::string>
    return std::nullopt;
}

auto CREG::getAccessTechnology() const noexcept -> Store::Network::AccessTechnology
auto Creg::getAccessTechnology() const noexcept -> Store::Network::AccessTechnology
{
    if (isValid() && isExtended()) {
        int accessTechnologyInt;

R module-cellular/at/src/URC_CTZE.cpp => module-cellular/at/src/UrcCtze.cpp +8 -23
@@ 1,7 1,7 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "../URC_CTZE.hpp"
#include "../UrcCtze.hpp"
#include <log/debug.hpp>
#include <module-utils/time/time_conversion.hpp>



@@ 9,27 9,12 @@

using namespace at::urc;

CTZE::CTZE(const std::string &val) : Any(val)
{
    if (!is()) {
        return;
    }
    for (auto &t : tokens) {
        utils::findAndReplaceAll(t, "\"", "");
    }
}

auto CTZE::what() const noexcept -> std::string
{
    return urc_name;
}

auto CTZE::isValid() const noexcept -> bool
auto Ctze::isValid() const noexcept -> bool
{
    return tokens.size() == magic_enum::enum_count<Tokens>();
}

int CTZE::getTimeZoneOffset() const
int Ctze::getTimeZoneOffset() const
{
    const std::string &tzOffsetToken = tokens[static_cast<uint32_t>(Tokens::GMTDifference)];



@@ 42,7 27,7 @@ int CTZE::getTimeZoneOffset() const
    }

    if (failed) {
        LOG_ERROR("Failed to parse CTZE time zone offset: %s", tzOffsetToken.c_str());
        LOG_ERROR("Failed to parse Ctze time zone offset: %s", tzOffsetToken.c_str());
    }

    int offsetInSeconds = offsetInQuartersOfHour * utils::time::minutesInQuarterOfHour * utils::time::secondsInMinute;


@@ 50,7 35,7 @@ int CTZE::getTimeZoneOffset() const
    return offsetInSeconds;
}

std::string CTZE::getTimeZoneString() const
std::string Ctze::getTimeZoneString() const
{
    std::string timeZoneStr = tokens[static_cast<uint32_t>(Tokens::GMTDifference)] + "," +
                              tokens[static_cast<uint32_t>(Tokens::DaylightSavingsAdjustment)];


@@ 58,19 43,19 @@ std::string CTZE::getTimeZoneString() const
    return timeZoneStr;
}

const struct tm CTZE::getGMTTime(void) const
const struct tm Ctze::getGMTTime(void) const
{
    struct tm timeinfo = {};
    std::stringstream stream(tokens[static_cast<uint32_t>(Tokens::Date)] + "," +
                             tokens[static_cast<uint32_t>(Tokens::Time)]);
    stream >> std::get_time(&timeinfo, "%Y/%m/%d,%H:%M:%S");
    if (stream.fail()) {
        LOG_ERROR("Failed to parse CTZE time");
        LOG_ERROR("Failed to parse Ctze time");
    }
    return timeinfo;
}

auto CTZE::getTimeInfo() const noexcept -> tm
auto Ctze::getTimeInfo() const noexcept -> tm
{
    using namespace std::chrono;


R module-cellular/at/src/URC_CUSD.cpp => module-cellular/at/src/UrcCusd.cpp +7 -15
@@ 1,26 1,18 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <URC_CUSD.hpp>
#include <UrcCusd.hpp>
#include <Utils.hpp>
#include <magic_enum.hpp>

using namespace at::urc;

CUSD::CUSD(const std::string &val) : Any(val)
{}

auto CUSD::what() const noexcept -> std::string
{
    return urc_name;
}

auto CUSD::isValid() const noexcept -> bool
auto Cusd::isValid() const noexcept -> bool
{
    return is() && tokens.size() == magic_enum::enum_count<Tokens>();
    return tokens.size() == magic_enum::enum_count<Tokens>();
}

auto CUSD::isActionNeeded() const noexcept -> bool
auto Cusd::isActionNeeded() const noexcept -> bool
{
    if (isValid()) {
        auto status = getStatus();


@@ 31,7 23,7 @@ auto CUSD::isActionNeeded() const noexcept -> bool
    return false;
}

auto CUSD::getMessage() const noexcept -> std::optional<std::string>
auto Cusd::getMessage() const noexcept -> std::optional<std::string>
{
    if (!isValid()) {
        return std::nullopt;


@@ 43,7 35,7 @@ auto CUSD::getMessage() const noexcept -> std::optional<std::string>
    return utils::trim(message);
}

auto CUSD::getStatus() const noexcept -> std::optional<StatusType>
auto Cusd::getStatus() const noexcept -> std::optional<StatusType>
{
    if (isValid()) {
        int statusInt;


@@ 63,7 55,7 @@ auto CUSD::getStatus() const noexcept -> std::optional<StatusType>
    return std::nullopt;
}

auto CUSD::getDCS() const noexcept -> std::optional<int>
auto Cusd::getDCS() const noexcept -> std::optional<int>
{
    if (!isValid()) {
        return std::nullopt;

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

#include <UrcFactory.hpp>

#include <UrcCreg.hpp>
#include <UrcCtze.hpp>
#include <UrcCusd.hpp>
#include <UrcQind.hpp>
#include <UrcCmti.hpp>
#include <UrcClip.hpp>
#include <UrcPoweredDown.hpp>
#include <UrcResponse.hpp>

using namespace at::urc;

std::unique_ptr<Urc> UrcFactory::Create(const std::string &urcMessage)
{
    if (urcMessage.empty()) {
        return std::make_unique<Urc>(std::string());
    }

    const char headDelimiter = ':';
    auto it                  = std::find(urcMessage.begin(), urcMessage.end(), headDelimiter);
    std::string head         = std::string(urcMessage.begin(), it);
    std::string body         = std::string(it == urcMessage.end() ? urcMessage.begin() : it + 1, urcMessage.end());

    if (Ctze::isURC(head)) {
        return std::make_unique<Ctze>(body);
    }
    else if (Creg::isURC(head)) {
        return std::make_unique<Creg>(body);
    }
    else if (Qind::isURC(head)) {
        return std::make_unique<Qind>(body);
    }
    else if (Cusd::isURC(head)) {
        return std::make_unique<Cusd>(body);
    }
    else if (Cmti::isURC(head)) {
        return std::make_unique<Cmti>(body);
    }
    else if (Clip::isURC(head)) {
        return std::make_unique<Clip>(body);
    }
    else if (PoweredDown::isURC(head)) {
        return std::make_unique<PoweredDown>(body);
    }
    else if (auto type = UrcResponse::isURC(head)) {
        return std::make_unique<UrcResponse>(type.value());
    }

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

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

#include <UrcPoweredDown.hpp>
#include <module-utils/Utils.hpp>

using namespace at::urc;

bool PoweredDown::isValid() const noexcept
{
    return isNormalPowerDown() || isImmediatePowerDown();
}

bool PoweredDown::isNormalPowerDown() const noexcept
{
    return urcBody.find(PoweredDown::head_normal) != std::string::npos;
}

bool PoweredDown::isImmediatePowerDown() const noexcept
{
    return urcBody.find(PoweredDown::head_immediate) != std::string::npos;
}

R module-cellular/at/src/URC_QIND.cpp => module-cellular/at/src/UrcQind.cpp +34 -12
@@ 1,28 1,50 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <URC_QIND.hpp>
#include <UrcQind.hpp>
#include <log/debug.hpp>

using namespace at::urc;

QIND::QIND(const std::string &val) : Any(val)
{}
auto Qind::isCsq() const noexcept -> bool
{
    if (tokens.size() == magic_enum::enum_count<enum CSQ>()) {
        return tokens[CSQ].find(type_csq) != std::string::npos;
    }
    return false;
}

auto QIND::what() const noexcept -> std::string
auto Qind::isFota() const noexcept -> bool
{
    return urc_name;
    return tokens.size() > 0 && tokens[Fota] == type_fota;
}

auto QIND::isCsq() const noexcept -> bool
auto Qind::isFotaValid() const noexcept -> bool
{
    if (tokens.size() == magic_enum::enum_count<enum CSQ>()) {
        return tokens[CSQ].find(type_csq) != std::string::npos;
    return isFota() && tokens.size() >= fotaMinTokenSize;
}

auto Qind::getFotaStage() const noexcept -> std::optional<FotaStage>
{
    if (isFotaValid()) {
        for (auto &stage : magic_enum::enum_values<FotaStage>()) {
            if (tokens[Stage] == magic_enum::enum_name(stage)) {
                return stage;
            }
        }
    }
    return false;
    return std::nullopt;
}

auto Qind::getFotaParameter() const noexcept -> std::string
{
    if (isFotaValid() && tokens.size() >= fotaMinTokenSize) {
        return tokens[Param];
    }
    return std::string();
}

auto QIND::validate(enum CSQ check) const noexcept -> bool
auto Qind::validate(enum CSQ check) const noexcept -> bool
{
    try {
        if (isCsq()) {


@@ 45,7 67,7 @@ auto QIND::validate(enum CSQ check) const noexcept -> bool
    return false;
}

auto QIND::getRSSI() const noexcept -> std::optional<int>
auto Qind::getRSSI() const noexcept -> std::optional<int>
{
    if (isCsq()) {
        int rssi;


@@ 64,7 86,7 @@ auto QIND::getRSSI() const noexcept -> std::optional<int>
    return std::nullopt;
}

auto QIND::getBER() const noexcept -> std::optional<int>
auto Qind::getBER() const noexcept -> std::optional<int>
{
    if (isCsq()) {
        int ber;

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

#include <UrcResponse.hpp>
#include <module-utils/Utils.hpp>

using namespace at::urc;

UrcResponse::URCResponseType UrcResponse::getURCResponseType() const noexcept
{
    return type;
}

M module-cellular/test/unittest_URC.cpp => module-cellular/test/unittest_URC.cpp +607 -224
@@ 10,81 10,99 @@
#include <catch2/catch.hpp>
#include <module-utils/time/time_conversion.hpp>

#include "URC_QIND.hpp"
#include "URC_CUSD.hpp"
#include "URC_CTZE.hpp"
#include "URC_CREG.hpp"

TEST_CASE("+QIND: csq")
#include "UrcQind.hpp"
#include "UrcCusd.hpp"
#include "UrcCtze.hpp"
#include "UrcCreg.hpp"
#include "UrcCmti.hpp"
#include "UrcClip.hpp"
#include "UrcPoweredDown.hpp"
#include "UrcResponse.hpp"
#include "UrcFactory.hpp"

template <typename urcType> static auto getURC(std::unique_ptr<at::urc::Urc> &urc) -> std::shared_ptr<urcType>
{
    SECTION("Not valid data")
    {
        auto qind = at::urc::QIND("Not valid");
        REQUIRE_FALSE(qind.is());
    if (urc) {
        auto &rawUrc = *urc.get();
        if (typeid(rawUrc) == typeid(urcType)) {
            return std::unique_ptr<urcType>{static_cast<urcType *>(urc.release())};
        }
    }

    return nullptr;
}

TEST_CASE("+Qind: csq")
{
    SECTION("CSQ")
    {
        auto qind = at::urc::QIND("+QIND:\"csq\",100,50");
        REQUIRE(qind.is());
        REQUIRE(qind.isCsq());
        REQUIRE(*qind.getRSSI() == 100);
        REQUIRE(*qind.getBER() == 50);
        auto urc  = at::urc::UrcFactory::Create("+QIND:\"csq\",100,50");
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isCsq());
        REQUIRE(*qind->getRSSI() == 100);
        REQUIRE(*qind->getBER() == 50);
    }

    SECTION("CSQ with white spaces")
    {
        auto qind = at::urc::QIND("+QIND: \"csq\" , 100 , 50 ");
        REQUIRE(qind.is());
        REQUIRE(qind.isCsq());
        REQUIRE(*qind.getRSSI() == 100);
        REQUIRE(*qind.getBER() == 50);
        auto urc  = at::urc::UrcFactory::Create("+QIND: \"csq\" , 100 , 50 ");
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isCsq());
        REQUIRE(*qind->getRSSI() == 100);
        REQUIRE(*qind->getBER() == 50);
    }

    SECTION("too short")
    {
        auto qind = at::urc::QIND("+QIND:\"csq\",100");
        REQUIRE(qind.is());
        REQUIRE_FALSE(qind.isCsq());
        REQUIRE_FALSE(qind.getRSSI());
        REQUIRE_FALSE(qind.getBER());
        auto urc  = at::urc::UrcFactory::Create("+QIND:\"csq\",100");
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE_FALSE(qind->isCsq());
        REQUIRE_FALSE(qind->getRSSI());
        REQUIRE_FALSE(qind->getBER());
    }

    SECTION("too long")
    {
        auto qind = at::urc::QIND("+QIND:\"csq\",100,50,25");
        REQUIRE(qind.is());
        REQUIRE_FALSE(qind.isCsq());
        REQUIRE_FALSE(qind.getRSSI());
        REQUIRE_FALSE(qind.getBER());
        auto urc  = at::urc::UrcFactory::Create("+QIND:\"csq\",100,50,25");
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE_FALSE(qind->isCsq());
        REQUIRE_FALSE(qind->getRSSI());
        REQUIRE_FALSE(qind->getBER());
    }

    SECTION("no integer values")
    {
        auto qind = at::urc::QIND("+QIND:\"csq\",abc,def");
        REQUIRE(qind.is());
        REQUIRE(qind.isCsq());
        REQUIRE_FALSE(qind.getRSSI());
        REQUIRE_FALSE(qind.getBER());
        auto urc  = at::urc::UrcFactory::Create("+QIND:\"csq\",abc,def");
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isCsq());
        REQUIRE_FALSE(qind->getRSSI());
        REQUIRE_FALSE(qind->getBER());
    }

    SECTION("not CSQ")
    {
        auto qind = at::urc::QIND("+QIND:\"qsc\",100,50");
        REQUIRE(qind.is());
        REQUIRE_FALSE(qind.isCsq());
        REQUIRE_FALSE(qind.getRSSI());
        REQUIRE_FALSE(qind.getBER());
        auto urc  = at::urc::UrcFactory::Create("+QIND:\"qsc\",100,50");
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE_FALSE(qind->isCsq());
        REQUIRE_FALSE(qind->getRSSI());
        REQUIRE_FALSE(qind->getBER());
    }

    SECTION("valid CSQ")
    {
        std::vector<int> vec = {0, 1, 2, 98, 100, 101, 198, 200};
        for (auto a : vec) {
            auto qind = at::urc::QIND("+QIND:\"csq\"," + std::to_string(a) + ",50");
            REQUIRE(qind.is());
            REQUIRE(qind.isCsq());
            REQUIRE(*qind.getRSSI() == a);
            auto urc  = at::urc::UrcFactory::Create("+QIND:\"csq\"," + std::to_string(a) + ",50");
            auto qind = getURC<at::urc::Qind>(urc);
            REQUIRE(qind);
            REQUIRE(qind->isCsq());
            REQUIRE(*qind->getRSSI() == a);
        }
    }



@@ 92,297 110,662 @@ TEST_CASE("+QIND: csq")
    {
        std::vector<std::uint32_t> vec = {99, 199};
        for (auto a : vec) {
            auto qind = at::urc::QIND("+QIND:\"csq\"," + std::to_string(a) + ",50");
            REQUIRE(qind.is());
            REQUIRE(qind.isCsq());
            REQUIRE_FALSE(qind.getRSSI());
            auto urc  = at::urc::UrcFactory::Create("+QIND:\"csq\"," + std::to_string(a) + ",50");
            auto qind = getURC<at::urc::Qind>(urc);
            REQUIRE(qind);
            REQUIRE(qind->isCsq());
            REQUIRE_FALSE(qind->getRSSI());
        }
    }

    SECTION("not valid BER")
    {
        int ber   = 99;
        auto qind = at::urc::QIND("+QIND:\"csq\",50," + std::to_string(ber));
        REQUIRE(qind.is());
        REQUIRE(qind.isCsq());
        REQUIRE(*qind.getRSSI() == 50);
        REQUIRE_FALSE(qind.getBER());
        auto urc  = at::urc::UrcFactory::Create("+QIND:\"csq\",50," + std::to_string(ber));
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isCsq());
        REQUIRE(*qind->getRSSI() == 50);
        REQUIRE_FALSE(qind->getBER());
    }
}

TEST_CASE("+CUSD")
TEST_CASE("+Qind: FOTA")
{
    SECTION("Not valid data")
    {
        auto cusd = at::urc::CUSD("Not valid");
        REQUIRE_FALSE(cusd.is());
    SECTION("FOTA")
    {
        // fota normal
        auto urc  = at::urc::UrcFactory::Create("+QIND:\"FOTA\",\"HTTPEND\",50");
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isFotaValid());
        // fota dirty
        urc  = at::urc::UrcFactory::Create("\r\r\n\n+QIND: \r\n\"FOTA\",\"HTTPEND\",50\n\n\r\r");
        qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isFotaValid());
    }

    SECTION("FOTA malformed")
    {
        auto urc  = at::urc::UrcFactory::Create("+QIND:\"FOTA\"");
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isFota());
        REQUIRE_FALSE(qind->isFotaValid());
    }

    SECTION("FOTA stage and parameter")
    {
        auto urc  = at::urc::UrcFactory::Create("+QIND: \"FOTA\",\"HTTPSTART\"");
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isFota());
        REQUIRE(qind->isFotaValid());
        REQUIRE(qind->getFotaStage() == at::urc::Qind::FotaStage::HTTPSTART);
        REQUIRE(qind->getFotaParameter().empty());

        urc  = at::urc::UrcFactory::Create("+QIND: \"FOTA\",\"HTTPEND\", 22");
        qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isFota());
        REQUIRE(qind->isFotaValid());
        REQUIRE(qind->getFotaStage() == at::urc::Qind::FotaStage::HTTPEND);
        REQUIRE(qind->getFotaParameter() == "22");

        urc  = at::urc::UrcFactory::Create("+QIND: \"FOTA\",\"START\"");
        qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isFota());
        REQUIRE(qind->isFotaValid());
        REQUIRE(qind->getFotaStage() == at::urc::Qind::FotaStage::START);
        REQUIRE(qind->getFotaParameter().empty());

        urc  = at::urc::UrcFactory::Create("+QIND: \"FOTA\",\"UPDATING\", 99");
        qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isFota());
        REQUIRE(qind->isFotaValid());
        REQUIRE(qind->getFotaStage() == at::urc::Qind::FotaStage::UPDATING);
        REQUIRE(qind->getFotaParameter() == "99");

        urc  = at::urc::UrcFactory::Create("+QIND: \"FOTA\",\"END\", 2");
        qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isFota());
        REQUIRE(qind->isFotaValid());
        REQUIRE(qind->getFotaStage() == at::urc::Qind::FotaStage::END);
        REQUIRE(qind->getFotaParameter() == "2");

        urc  = at::urc::UrcFactory::Create("+QIND: \"FOTA\",\"TEST\", 3");
        qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isFota());
        REQUIRE(qind->isFotaValid());
        REQUIRE_FALSE(qind->getFotaStage());
        REQUIRE(qind->getFotaParameter() == "3");
    }

    SECTION("FOTA dirty")
    {
        auto urc =
            at::urc::UrcFactory::Create("\n\r+QIND: \n\r\r \"FOTA\" ,\r \n \"HTTPEND\", \n\r     test test \r\n");
        auto qind = getURC<at::urc::Qind>(urc);
        REQUIRE(qind);
        REQUIRE(qind->isFota());
        REQUIRE(qind->isFotaValid());
        REQUIRE(qind->getFotaStage() == at::urc::Qind::FotaStage::HTTPEND);
        REQUIRE(qind->getFotaParameter() == "test test");
    }
}

    SECTION("CUSD action needed")
TEST_CASE("+Cusd")
{
    SECTION("Cusd action needed")
    {
        auto cusd = at::urc::CUSD("+CUSD:1,\"test msg\",14");
        REQUIRE(cusd.is());
        REQUIRE(cusd.isValid());
        REQUIRE(cusd.isActionNeeded());
        REQUIRE(*cusd.getMessage() == "test msg");
        REQUIRE(*cusd.getStatus() == at::urc::CUSD::StatusType::FurtherUserActionRequired);
        REQUIRE(*cusd.getDCS() == 14);
        auto urc  = at::urc::UrcFactory::Create("+CUSD:1,\"test msg\",14");
        auto cusd = getURC<at::urc::Cusd>(urc);

        REQUIRE(cusd);
        REQUIRE(cusd->isValid());
        REQUIRE(cusd->isActionNeeded());
        REQUIRE(*cusd->getMessage() == "test msg");
        REQUIRE(*cusd->getStatus() == at::urc::Cusd::StatusType::FurtherUserActionRequired);
        REQUIRE(*cusd->getDCS() == 14);
    }

    SECTION("CUSD action needed with white spaces")
    SECTION("Cusd action needed with white spaces")
    {
        auto cusd = at::urc::CUSD("+CUSD: 0 , \"test msg\" , 15 ");
        REQUIRE(cusd.is());
        REQUIRE(cusd.isValid());
        REQUIRE_FALSE(cusd.isActionNeeded());
        REQUIRE(*cusd.getMessage() == "test msg");
        REQUIRE(*cusd.getStatus() == at::urc::CUSD::StatusType::NoFurtherUserActionRequired);
        REQUIRE(*cusd.getDCS() == 15);
        auto urc  = at::urc::UrcFactory::Create("+CUSD: 0 , \"test msg\" , 15 ");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE(cusd);
        REQUIRE(cusd->isValid());
        REQUIRE_FALSE(cusd->isActionNeeded());
        REQUIRE(*cusd->getMessage() == "test msg");
        REQUIRE(*cusd->getStatus() == at::urc::Cusd::StatusType::NoFurtherUserActionRequired);
        REQUIRE(*cusd->getDCS() == 15);
    }

    SECTION("CUSD wrong status and DCS")
    SECTION("Cusd wrong status and DCS")
    {
        auto cusd = at::urc::CUSD("+CUSD:100,\"test msg\", abc");
        REQUIRE(cusd.is());
        REQUIRE(cusd.isValid());
        REQUIRE_FALSE(cusd.isActionNeeded());
        REQUIRE(*cusd.getMessage() == "test msg");
        REQUIRE_FALSE(cusd.getStatus());
        REQUIRE_FALSE(cusd.getDCS());
        auto urc  = at::urc::UrcFactory::Create("+CUSD:100,\"test msg\", abc");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE(cusd);
        REQUIRE(cusd->isValid());
        REQUIRE_FALSE(cusd->isActionNeeded());
        REQUIRE(*cusd->getMessage() == "test msg");
        REQUIRE_FALSE(cusd->getStatus());
        REQUIRE_FALSE(cusd->getDCS());
    }

    SECTION("CUSD action not needed")
    SECTION("Cusd action not needed")
    {
        auto cusd = at::urc::CUSD("+CUSD:2,\"test msg\",15");
        REQUIRE(cusd.is());
        REQUIRE(cusd.isValid());
        REQUIRE_FALSE(cusd.isActionNeeded());
        auto urc  = at::urc::UrcFactory::Create("+CUSD:2,\"test msg\",15");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE(cusd);
        REQUIRE(cusd->isValid());
        REQUIRE_FALSE(cusd->isActionNeeded());
    }

    SECTION("no CUSD")
    SECTION("no Cusd")
    {
        auto cusd = at::urc::CUSD("+CSUD:1,\"test msg\",15");
        REQUIRE_FALSE(cusd.is());
        auto urc  = at::urc::UrcFactory::Create("+CSUD:1,\"test msg\",15");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE_FALSE(cusd);
    }

    SECTION("too short")
    {
        auto cusd = at::urc::CUSD("+CUSD:1,\"test msg\"");
        REQUIRE(cusd.is());
        REQUIRE_FALSE(cusd.isValid());
        auto urc  = at::urc::UrcFactory::Create("+CUSD:1,\"test msg\"");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE(cusd);
        REQUIRE_FALSE(cusd->isValid());
    }

    SECTION("too long")
    {
        auto cusd = at::urc::CUSD("+CUSD:1,\"test msg\",15,15");
        REQUIRE(cusd.is());
        REQUIRE_FALSE(cusd.isValid());
        auto urc  = at::urc::UrcFactory::Create("+CUSD:1,\"test msg\",15,15");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE(cusd);
        REQUIRE_FALSE(cusd->isValid());
    }
}

TEST_CASE("+CTZE")
TEST_CASE("+Ctze")
{
    SECTION("Not valid data")
    {
        auto ctze = at::urc::CTZE("Not valid");
        REQUIRE_FALSE(ctze.is());
        REQUIRE_FALSE(ctze.isValid());
        auto urc  = at::urc::UrcFactory::Create("+QIND:\"csq\"");
        auto ctze = getURC<at::urc::Ctze>(urc);
        REQUIRE_FALSE(ctze);
    }

    SECTION("ctze +08")
    {
        auto ctze = at::urc::CTZE("+CTZE: \"+08\",1,\"2020/10/21,13:49:57\"");
        REQUIRE(ctze.is());
        REQUIRE(ctze.isValid());
        auto timeInfo = ctze.getTimeInfo();
        auto urc  = at::urc::UrcFactory::Create("+CTZE: \"+08\",1,\"2020/10/21,13:49:57\"");
        auto ctze = getURC<at::urc::Ctze>(urc);
        REQUIRE(ctze);
        REQUIRE(ctze->isValid());
        auto timeInfo = ctze->getTimeInfo();
        std::stringstream ss;
        ss << std::put_time(&timeInfo, "%Y/%m/%d,%H:%M:%S");
        REQUIRE(ss.str() == "2020/10/21,15:49:57");

        REQUIRE(ctze.getTimeZoneOffset() == 8 * utils::time::minutesInQuarterOfHour * utils::time::secondsInMinute);
        REQUIRE(ctze.getTimeZoneString() == "+08,1");
        REQUIRE(ctze->getTimeZoneOffset() == 8 * utils::time::minutesInQuarterOfHour * utils::time::secondsInMinute);
        REQUIRE(ctze->getTimeZoneString() == "+08,1");
        ss.str(std::string());
        timeInfo = ctze.getGMTTime();
        timeInfo = ctze->getGMTTime();
        ss << std::put_time(&timeInfo, "%Y/%m/%d,%H:%M:%S");
        REQUIRE(ss.str() == "2020/10/21,13:49:57");
    }

    SECTION("ctze -08")
    {
        auto ctze = at::urc::CTZE("+CTZE: \"-08\",1,\"2020/10/21,13:49:57\"");
        REQUIRE(ctze.is());
        REQUIRE(ctze.isValid());
        auto timeInfo = ctze.getTimeInfo();
        auto urc  = at::urc::UrcFactory::Create("+CTZE: \"-08\",1,\"2020/10/21,13:49:57\"");
        auto ctze = getURC<at::urc::Ctze>(urc);
        REQUIRE(ctze);
        REQUIRE(ctze->isValid());
        auto timeInfo = ctze->getTimeInfo();
        std::ostringstream ss;
        ss << std::put_time(&timeInfo, "%Y/%m/%d,%H:%M:%S");
        REQUIRE(ss.str() == "2020/10/21,11:49:57");

        REQUIRE(ctze.getTimeZoneOffset() == -8 * utils::time::minutesInQuarterOfHour * utils::time::secondsInMinute);
        REQUIRE(ctze.getTimeZoneString() == "-08,1");
        REQUIRE(ctze->getTimeZoneOffset() == -8 * utils::time::minutesInQuarterOfHour * utils::time::secondsInMinute);
        REQUIRE(ctze->getTimeZoneString() == "-08,1");
        ss.str(std::string());
        timeInfo = ctze.getGMTTime();
        timeInfo = ctze->getGMTTime();
        ss << std::put_time(&timeInfo, "%Y/%m/%d,%H:%M:%S");
        REQUIRE(ss.str() == "2020/10/21,13:49:57");
    }

    SECTION("ctze -00")
    {
        auto ctze = at::urc::CTZE("+CTZE: \"-00\",0,\"2020/10/21,13:49:57\"");
        REQUIRE(ctze.is());
        REQUIRE(ctze.isValid());
        auto timeInfo = ctze.getTimeInfo();
        auto urc  = at::urc::UrcFactory::Create("+CTZE: \"-00\",0,\"2020/10/21,13:49:57\"");
        auto ctze = getURC<at::urc::Ctze>(urc);
        REQUIRE(ctze);
        REQUIRE(ctze->isValid());
        auto timeInfo = ctze->getTimeInfo();
        std::ostringstream ss;
        ss << std::put_time(&timeInfo, "%Y/%m/%d,%H:%M:%S");
        REQUIRE(ss.str() == "2020/10/21,13:49:57");

        REQUIRE(ctze.getTimeZoneOffset() == 0);
        REQUIRE(ctze.getTimeZoneString() == "-00,0");
        REQUIRE(ctze->getTimeZoneOffset() == 0);
        REQUIRE(ctze->getTimeZoneString() == "-00,0");
        ss.str(std::string());
        timeInfo = ctze.getGMTTime();
        timeInfo = ctze->getGMTTime();
        ss << std::put_time(&timeInfo, "%Y/%m/%d,%H:%M:%S");
        REQUIRE(ss.str() == "2020/10/21,13:49:57");
    }

    SECTION("too short")
    {
        auto ctze = at::urc::CTZE("+CTZE: \"-08\",\"2020/10/21,13:49:57\"");
        REQUIRE(ctze.is());
        REQUIRE_FALSE(ctze.isValid());
        // auto timeInfo = ctze.getTimeInfo();
        auto urc  = at::urc::UrcFactory::Create("+CTZE: \"-08\",\"2020/10/21,13:49:57\"");
        auto ctze = getURC<at::urc::Ctze>(urc);
        REQUIRE(ctze);
        REQUIRE_FALSE(ctze->isValid());
        // auto timeInfo = ctze->getTimeInfo();
    }
}

TEST_CASE("+CREG")
TEST_CASE("+Creg")
{
    SECTION("Not valid data")
    SECTION("Creg short")
    {
        auto creg = at::urc::CUSD("Not valid");
        REQUIRE_FALSE(creg.is());
        auto urc  = at::urc::UrcFactory::Create("+CREG: 0");
        auto creg = getURC<at::urc::Creg>(urc);
        REQUIRE(creg);
        REQUIRE(creg->isValid());
        REQUIRE(creg->isShort());
        REQUIRE_FALSE(creg->isExtended());
        REQUIRE(creg->getStatus() == Store::Network::Status::NotRegistered);
        REQUIRE_FALSE(creg->getLocation());
        REQUIRE_FALSE(creg->getCellId());
        REQUIRE(creg->getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
    }

    SECTION("CREG short")
    SECTION("Creg extended")
    {
        auto creg = at::urc::CREG("+CREG: 0");
        REQUIRE(creg.is());
        REQUIRE(creg.isValid());
        REQUIRE(creg.isShort());
        REQUIRE_FALSE(creg.isExtended());
        REQUIRE(creg.getStatus() == Store::Network::Status::NotRegistered);
        REQUIRE_FALSE(creg.getLocation());
        REQUIRE_FALSE(creg.getCellId());
        REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
        auto urc  = at::urc::UrcFactory::Create("+CREG: 1,\"D509\",\"80D413D\",7");
        auto creg = getURC<at::urc::Creg>(urc);
        REQUIRE(creg);
        REQUIRE(creg->isValid());
        REQUIRE_FALSE(creg->isShort());
        REQUIRE(creg->isExtended());
        REQUIRE(creg->getStatus() == Store::Network::Status::RegisteredHomeNetwork);
        REQUIRE(*creg->getLocation() == "D509");
        REQUIRE(*creg->getCellId() == "80D413D");
        REQUIRE(creg->getAccessTechnology() == Store::Network::AccessTechnology::EUtran);
    }

    SECTION("CREG extended")
    {
        auto creg = at::urc::CREG("+CREG: 1,\"D509\",\"80D413D\",7");
        REQUIRE(creg.is());
        REQUIRE(creg.isValid());
        REQUIRE_FALSE(creg.isShort());
        REQUIRE(creg.isExtended());
        REQUIRE(creg.getStatus() == Store::Network::Status::RegisteredHomeNetwork);
        REQUIRE(*creg.getLocation() == "D509");
        REQUIRE(*creg.getCellId() == "80D413D");
        REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::EUtran);
    }

    SECTION("CREG extended access technology")
    SECTION("Creg extended access technology")
    {
        {
            auto creg = at::urc::CREG("+CREG: 1,\"D509\",\"80D413D\",0");
            REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::Gsm);
            auto urc  = at::urc::UrcFactory::Create("+CREG: 1,\"D509\",\"80D413D\",0");
            auto creg = getURC<at::urc::Creg>(urc);
            REQUIRE(creg->getAccessTechnology() == Store::Network::AccessTechnology::Gsm);
        }
        {
            auto creg = at::urc::CREG("+CREG: 1,\"D509\",\"80D413D\",1");
            REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
            auto urc  = at::urc::UrcFactory::Create("+CREG: 1,\"D509\",\"80D413D\",1");
            auto creg = getURC<at::urc::Creg>(urc);
            REQUIRE(creg->getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
        }
        {
            auto creg = at::urc::CREG("+CREG: 1,\"D509\",\"80D413D\",7");
            REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::EUtran);
            auto urc  = at::urc::UrcFactory::Create("+CREG: 1,\"D509\",\"80D413D\",7");
            auto creg = getURC<at::urc::Creg>(urc);
            REQUIRE(creg->getAccessTechnology() == Store::Network::AccessTechnology::EUtran);
        }
        {
            auto creg = at::urc::CREG("+CREG: 1,\"D509\",\"80D413D\",8");
            REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
            auto urc  = at::urc::UrcFactory::Create("+CREG: 1,\"D509\",\"80D413D\",8");
            auto creg = getURC<at::urc::Creg>(urc);
            REQUIRE(creg->getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
        }
        {
            auto creg = at::urc::CREG("+CREG: 1,\"D509\",\"80D413D\",ABX");
            REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
            auto urc  = at::urc::UrcFactory::Create("+CREG: 1,\"D509\",\"80D413D\",ABX");
            auto creg = getURC<at::urc::Creg>(urc);
            REQUIRE(creg->getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
        }
    }

    SECTION("CREG status")
    SECTION("Creg status")
    {
        {
            auto creg = at::urc::CREG("+CREG: 0,\"D509\",\"80D413D\",0");
            REQUIRE(creg.getStatus() == Store::Network::Status::NotRegistered);
            auto urc  = at::urc::UrcFactory::Create("+CREG: 0,\"D509\",\"80D413D\",0");
            auto creg = getURC<at::urc::Creg>(urc);
            REQUIRE(creg->getStatus() == Store::Network::Status::NotRegistered);
        }
        {
            auto creg = at::urc::CREG("+CREG: 5,\"D509\",\"80D413D\",0");
            REQUIRE(creg.getStatus() == Store::Network::Status::RegisteredRoaming);
            auto urc  = at::urc::UrcFactory::Create("+CREG: 5,\"D509\",\"80D413D\",0");
            auto creg = getURC<at::urc::Creg>(urc);
            REQUIRE(creg->getStatus() == Store::Network::Status::RegisteredRoaming);
        }
        {
            auto creg = at::urc::CREG("+CREG: 4,\"D509\",\"80D413D\",0");
            REQUIRE(creg.getStatus() == Store::Network::Status::Unknown);
            auto urc  = at::urc::UrcFactory::Create("+CREG: 4,\"D509\",\"80D413D\",0");
            auto creg = getURC<at::urc::Creg>(urc);
            REQUIRE(creg->getStatus() == Store::Network::Status::Unknown);
        }
        {
            auto creg = at::urc::CREG("+CREG: 6,\"D509\",\"80D413D\",0");
            REQUIRE(creg.getStatus() == Store::Network::Status::Unknown);
            auto urc  = at::urc::UrcFactory::Create("+CREG: 6,\"D509\",\"80D413D\",0");
            auto creg = getURC<at::urc::Creg>(urc);
            REQUIRE(creg->getStatus() == Store::Network::Status::Unknown);
        }
        {
            auto creg = at::urc::CREG("+CREG: A,\"D509\",\"80D413D\",0");
            REQUIRE(creg.getStatus() == Store::Network::Status::Unknown);
            auto urc  = at::urc::UrcFactory::Create("+CREG: A,\"D509\",\"80D413D\",0");
            auto creg = getURC<at::urc::Creg>(urc);
            REQUIRE(creg->getStatus() == Store::Network::Status::Unknown);
        }
    }

    SECTION("CREG no CREG")
    {
        auto creg = at::urc::CREG("+CEGR: 0");
        REQUIRE_FALSE(creg.is());
        REQUIRE_FALSE(creg.isValid());
        REQUIRE(creg.isShort());
        REQUIRE_FALSE(creg.isExtended());
        REQUIRE(creg.getStatus() == Store::Network::Status::Unknown);
        REQUIRE_FALSE(creg.getLocation());
        REQUIRE_FALSE(creg.getCellId());
        REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
    }

    SECTION("CREG too short")
    {
        auto creg = at::urc::CREG("+CREG:");
        REQUIRE(creg.is());
        REQUIRE_FALSE(creg.isValid());
        REQUIRE_FALSE(creg.isShort());
        REQUIRE_FALSE(creg.isExtended());
        REQUIRE(creg.getStatus() == Store::Network::Status::Unknown);
        REQUIRE_FALSE(creg.getLocation());
        REQUIRE_FALSE(creg.getCellId());
        REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
    }

    SECTION("CREG too long")
    {
        auto creg = at::urc::CREG("+CREG: 0,\"D509\",\"80D413D\",0,1");
        REQUIRE(creg.is());
        REQUIRE_FALSE(creg.isValid());
        REQUIRE_FALSE(creg.isShort());
        REQUIRE_FALSE(creg.isExtended());
        REQUIRE(creg.getStatus() == Store::Network::Status::Unknown);
        REQUIRE_FALSE(creg.getLocation());
        REQUIRE_FALSE(creg.getCellId());
        REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
    }

    SECTION("CREG wrong length")
    {
        auto creg = at::urc::CREG("+CREG: 0,\"D509\"");
        REQUIRE(creg.is());
        REQUIRE_FALSE(creg.isValid());
        REQUIRE_FALSE(creg.isShort());
        REQUIRE_FALSE(creg.isExtended());
        REQUIRE(creg.getStatus() == Store::Network::Status::Unknown);
        REQUIRE_FALSE(creg.getLocation());
        REQUIRE_FALSE(creg.getCellId());
        REQUIRE(creg.getAccessTechnology() == Store::Network::AccessTechnology::Unknown);
    SECTION("Creg no Creg")
    {
        auto urc  = at::urc::UrcFactory::Create("+CEGR: 0");
        auto creg = getURC<at::urc::Creg>(urc);
        REQUIRE_FALSE(creg);
    }

    SECTION("Creg too short")
    {
        auto urc  = at::urc::UrcFactory::Create("+CREG:");
        auto creg = getURC<at::urc::Creg>(urc);
        REQUIRE(creg);
        REQUIRE_FALSE(creg->isValid());
    }

    SECTION("Creg too long")
    {
        auto urc  = at::urc::UrcFactory::Create("+CREG: 0,\"D509\",\"80D413D\",0,1");
        auto creg = getURC<at::urc::Creg>(urc);
        REQUIRE(creg);
        REQUIRE_FALSE(creg->isValid());
    }

    SECTION("Creg wrong length")
    {
        auto urc  = at::urc::UrcFactory::Create("+CREG 0,\"D509\"");
        auto creg = getURC<at::urc::Creg>(urc);
        REQUIRE(creg);
        REQUIRE_FALSE(creg->isValid());
    }
}

TEST_CASE("+Cmti")
{
    SECTION("Cmti too short")
    {
        auto urc  = at::urc::UrcFactory::Create("+CMTI: \"ME\"");
        auto cmti = getURC<at::urc::Cmti>(urc);
        REQUIRE(cmti);
        REQUIRE_FALSE(cmti->isValid());
    }

    SECTION("Cmti too long")
    {
        auto urc  = at::urc::UrcFactory::Create("+CMTI: \"ME\",1,1");
        auto cmti = getURC<at::urc::Cmti>(urc);
        REQUIRE(cmti);
        REQUIRE_FALSE(cmti->isValid());
    }

    SECTION("Cmti valid")
    {
        auto urc  = at::urc::UrcFactory::Create("+CMTI: \"ME\",1");
        auto cmti = getURC<at::urc::Cmti>(urc);
        REQUIRE(cmti);
        REQUIRE(cmti->isValid());
        REQUIRE(cmti->getIndex() == "1");
        REQUIRE(cmti->getMemory() == "ME");
    }
}

TEST_CASE("+CLIP")
{
    SECTION("CLIP too short")
    {
        auto urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\"");
        auto clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE_FALSE(clip->isValid());
    }

    SECTION("CLIP valid")
    {
        // two parameters
        auto urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",145");
        auto clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());

        // three parameters
        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",145,");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE_FALSE(clip->getSubaddr());
        REQUIRE_FALSE(clip->getAlpha());
        REQUIRE_FALSE(clip->getSatype());
        REQUIRE_FALSE(clip->getCLIValidity());

        // four parameters
        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",145,,");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE_FALSE(clip->getSubaddr());
        REQUIRE_FALSE(clip->getAlpha());
        REQUIRE_FALSE(clip->getSatype());
        REQUIRE_FALSE(clip->getCLIValidity());

        // six parameters
        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",145,,,");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE_FALSE(clip->getSubaddr());
        REQUIRE_FALSE(clip->getAlpha());
        REQUIRE_FALSE(clip->getSatype());
        REQUIRE_FALSE(clip->getCLIValidity());

        // seven parameters
        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",145,,,,");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE_FALSE(clip->getSubaddr());
        REQUIRE_FALSE(clip->getAlpha());
        REQUIRE_FALSE(clip->getSatype());
        REQUIRE_FALSE(clip->getCLIValidity());

        // two parameters with validation not empty
        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",145,,,,0");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE_FALSE(clip->getSubaddr());
        REQUIRE_FALSE(clip->getAlpha());
        REQUIRE_FALSE(clip->getSatype());
        REQUIRE(clip->getCLIValidity() == "0");
    }

    SECTION("CLIP phone number")
    {
        auto urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",145,,,,0");
        auto clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE(clip->getNumber() == "+48123456789");

        urc  = at::urc::UrcFactory::Create("+CLIP: \"\",145,,,,0");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE(clip->getNumber() == "");

        urc  = at::urc::UrcFactory::Create("+CLIP: ,145,,,,0");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE(clip->getNumber() == "");
    }

    SECTION("CLIP address type")
    {
        auto urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",,,,,0");
        auto clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE_FALSE(clip->getType());

        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",577,,,,0");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE_FALSE(clip->getType());

        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",\"test\",,,,0");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE_FALSE(clip->getType());

        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",test,,,,0");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE_FALSE(clip->getType());

        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",129,,,,0");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE(clip->getType() == at::urc::Clip::AddressType::UnknownType);

        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",145,,,,0");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE(clip->getType() == at::urc::Clip::AddressType::InternationalNumber);

        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",161,,,,0");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE(clip->getType() == at::urc::Clip::AddressType::NationalNumber);
    }

    SECTION("CLIP optional parameters")
    {
        auto urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",145,,,,");
        auto clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE_FALSE(clip->getAlpha());
        REQUIRE_FALSE(clip->getSatype());
        REQUIRE_FALSE(clip->getSubaddr());
        REQUIRE_FALSE(clip->getCLIValidity());

        urc  = at::urc::UrcFactory::Create("+CLIP: \"+48123456789\",145,1,2,3,0");
        clip = getURC<at::urc::Clip>(urc);
        REQUIRE(clip);
        REQUIRE(clip->isValid());
        REQUIRE(clip->getAlpha() == "3");
        REQUIRE(clip->getSatype() == "2");
        REQUIRE(clip->getSubaddr() == "1");
        REQUIRE(clip->getCLIValidity() == "0");
    }
}

TEST_CASE("POWERED DOWN")
{
    SECTION("POWERED DOWN valid")
    {
        auto urc = at::urc::UrcFactory::Create("POWERED DOWN");
        auto pwd = getURC<at::urc::PoweredDown>(urc);
        REQUIRE(pwd);
        REQUIRE(pwd->isValid());
        REQUIRE(pwd->isImmediatePowerDown());
        REQUIRE_FALSE(pwd->isNormalPowerDown());

        urc = at::urc::UrcFactory::Create("NORMAL POWER DOWN");
        pwd = getURC<at::urc::PoweredDown>(urc);
        REQUIRE(pwd);
        REQUIRE(pwd->isValid());
        REQUIRE_FALSE(pwd->isImmediatePowerDown());
        REQUIRE(pwd->isNormalPowerDown());

        // dirty
        urc = at::urc::UrcFactory::Create("\n\r POWERED DOWN\n\r ");
        pwd = getURC<at::urc::PoweredDown>(urc);
        REQUIRE(pwd);
        REQUIRE(pwd->isValid());
        REQUIRE(pwd->isImmediatePowerDown());
        REQUIRE_FALSE(pwd->isNormalPowerDown());

        urc = at::urc::UrcFactory::Create(" \n\r  NORMAL POWER DOWN \n\r ");
        pwd = getURC<at::urc::PoweredDown>(urc);
        REQUIRE(pwd);
        REQUIRE(pwd->isValid());
        REQUIRE_FALSE(pwd->isImmediatePowerDown());
        REQUIRE(pwd->isNormalPowerDown());
    }

    SECTION("POWERED DOWN invalid")
    {
        auto pwd = at::urc::PoweredDown("TEST");
        REQUIRE_FALSE(pwd.isValid());
        REQUIRE_FALSE(pwd.isImmediatePowerDown());
        REQUIRE_FALSE(pwd.isNormalPowerDown());
    }
}

TEST_CASE("Urc RESPONSE")
{
    SECTION("Urc RESPONSE valid")
    {
        auto urc = at::urc::UrcFactory::Create("OK");
        auto rsp = getURC<at::urc::UrcResponse>(urc);
        REQUIRE(rsp);
        REQUIRE(rsp->getURCResponseType() == at::urc::UrcResponse::URCResponseType::Ok);

        urc = at::urc::UrcFactory::Create("CONNECT");
        rsp = getURC<at::urc::UrcResponse>(urc);
        REQUIRE(rsp);
        REQUIRE(rsp->getURCResponseType() == at::urc::UrcResponse::URCResponseType::Connect);

        urc = at::urc::UrcFactory::Create("RING");
        rsp = getURC<at::urc::UrcResponse>(urc);
        REQUIRE(rsp);
        REQUIRE(rsp->getURCResponseType() == at::urc::UrcResponse::URCResponseType::Ring);

        urc = at::urc::UrcFactory::Create("NO CARRIER");
        rsp = getURC<at::urc::UrcResponse>(urc);
        REQUIRE(rsp);
        REQUIRE(rsp->getURCResponseType() == at::urc::UrcResponse::URCResponseType::NoCarrier);

        urc = at::urc::UrcFactory::Create("ERROR");
        rsp = getURC<at::urc::UrcResponse>(urc);
        REQUIRE(rsp);
        REQUIRE(rsp->getURCResponseType() == at::urc::UrcResponse::URCResponseType::Error);

        urc = at::urc::UrcFactory::Create("NO DIALTONE");
        rsp = getURC<at::urc::UrcResponse>(urc);
        REQUIRE(rsp);
        REQUIRE(rsp->getURCResponseType() == at::urc::UrcResponse::URCResponseType::NoDialtone);

        urc = at::urc::UrcFactory::Create("BUSY");
        rsp = getURC<at::urc::UrcResponse>(urc);
        REQUIRE(rsp);
        REQUIRE(rsp->getURCResponseType() == at::urc::UrcResponse::URCResponseType::Busy);

        urc = at::urc::UrcFactory::Create("NO ANSWER");
        rsp = getURC<at::urc::UrcResponse>(urc);
        REQUIRE(rsp);
        REQUIRE(rsp->getURCResponseType() == at::urc::UrcResponse::URCResponseType::NoAnswer);
    }

    SECTION("Urc RESPONSE dirty")
    {
        auto urc = at::urc::UrcFactory::Create("\n\n \rNO ANSWER\n\r\n");
        auto rsp = getURC<at::urc::UrcResponse>(urc);
        REQUIRE(rsp);
        REQUIRE(rsp->getURCResponseType() == at::urc::UrcResponse::URCResponseType::NoAnswer);
    }
}

M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +1 -0
@@ 16,6 16,7 @@ target_sources( ${PROJECT_NAME}

	PRIVATE
		"${CMAKE_CURRENT_LIST_DIR}/ServiceCellular.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/CellularUrcHandler.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/api/CellularServiceAPI.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/CellularCall.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/SignalStrength.cpp"

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

#include "CellularUrcHandler.hpp"

#include "messages/CellularMessage.hpp"
#include "api/CellularServiceAPI.hpp"

#include <service-evtmgr/Constants.hpp>                              // for evt_manager service name
#include <module-sys/Service/Bus.hpp>                                // for sys::Bus
#include <module-services/service-antenna/api/AntennaServiceAPI.hpp> // for AntennaServiceAPI

// this static function will be replaced by Settings API
static bool isSettingsAutomaticTimeSyncEnabled()
{
    return true;
}

void CellularUrcHandler::Handle(Clip &urc)
{
    LOG_TRACE("incoming call...");
    std::string phoneNumber;
    if (urc.isValid()) {
        phoneNumber = urc.getNumber();
    }
    // continue without number
    response = std::make_unique<CellularCallMessage>(CellularCallMessage::Type::IncomingCall, phoneNumber);
    urc.setHandled(true);
}

void CellularUrcHandler::Handle(Creg &urc)
{
    if (urc.isValid()) {
        auto accessTechnology = urc.getAccessTechnology();
        auto status           = urc.getStatus();

        LOG_INFO("Network status - %s, access technology %s",
                 utils::enumToString(status).c_str(),
                 utils::enumToString(accessTechnology).c_str());

        Store::Network network{status, accessTechnology};

        Store::GSM::get()->setNetwork(network);
        response =
            std::make_unique<CellularNotificationMessage>(CellularNotificationMessage::Type::NetworkStatusUpdate);
        urc.setHandled(true);
    }
    else {
        LOG_WARN("Network status - not valid");
    }
}

void CellularUrcHandler::Handle(Cmti &urc)
{
    LOG_TRACE("received new SMS notification");
    if (urc.isValid()) {
        response = std::make_unique<CellularNotificationMessage>(CellularNotificationMessage::Type::NewIncomingSMS,
                                                                 urc.getIndex());
        urc.setHandled(true);
    }
    else {
        LOG_ERROR("Could not parse Cmti message");
    }
}

void CellularUrcHandler::Handle(Cusd &urc)
{
    if (urc.isActionNeeded()) {
        if (cellularService.ussdState == ussd::State::pullRequestSent) {
            cellularService.ussdState = ussd::State::pullResponseReceived;
            cellularService.setUSSDTimer();
        }
    }
    else {
        CellularServiceAPI::USSDRequest(&cellularService, CellularUSSDMessage::RequestType::abortSesion);
        cellularService.ussdState = ussd::State::sesionAborted;
        cellularService.setUSSDTimer();
    }

    auto message = urc.getMessage();
    if (!message) {
        response = std::nullopt;
        return;
    }

    response =
        std::make_unique<CellularNotificationMessage>(CellularNotificationMessage::Type::NewIncomingUSSD, *message);
    urc.setHandled(true);
}

void CellularUrcHandler::Handle(Ctze &urc)
{
    if (!urc.isValid()) {
        return;
    }

    if (isSettingsAutomaticTimeSyncEnabled()) {
        auto msg = std::make_shared<CellularTimeNotificationMessage>(
            urc.getGMTTime(), urc.getTimeZoneOffset(), urc.getTimeZoneString());
        sys::Bus::SendUnicast(msg, service::name::evt_manager, &cellularService);
    }
    else {
        LOG_DEBUG("Timezone sync disabled.");
    }
    urc.setHandled(true);
}

void CellularUrcHandler::Handle(Qind &urc)
{
    if (urc.isCsq()) {
        // Received signal strength change
        AntennaServiceAPI::CSQChange(&cellularService);
        auto rssi = urc.getRSSI();
        if (!rssi) {
            LOG_INFO("Invalid csq - ignore");
        }
        else {
            SignalStrength signalStrength(*rssi);

            Store::GSM::get()->setSignalStrength(signalStrength.data);
            response = std::make_unique<CellularNotificationMessage>(
                CellularNotificationMessage::Type::SignalStrengthUpdate, urc.getUrcBody());
        }
        urc.setHandled(true);
    }
    else if (urc.isFota()) {
        std::string httpSuccess = "0";
        if (urc.getFotaStage() == Qind::FotaStage::HTTPEND && urc.getFotaParameter() == httpSuccess) {
            LOG_DEBUG("Fota UPDATE, switching to AT mode");
            cellularService.cmux->setMode(TS0710::Mode::AT);
            urc.setHandled(true);
        }
    }
}

void CellularUrcHandler::Handle(PoweredDown &urc)
{
    if (urc.isValid()) {
        response =
            std::make_unique<CellularNotificationMessage>(CellularNotificationMessage::Type::PowerDownDeregistering);
        urc.setHandled(true);
    }
}

void CellularUrcHandler::Handle(UrcResponse &urc)
{
    std::vector<UrcResponse::URCResponseType> typesToHandle = {
        UrcResponse::URCResponseType::NoCarrier,
        UrcResponse::URCResponseType::Busy,
        UrcResponse::URCResponseType::NoAnswer,
    };

    for (auto &t : typesToHandle) {
        if (t == urc.getURCResponseType()) {
            LOG_TRACE("call aborted");
            response = std::make_unique<CellularNotificationMessage>(CellularNotificationMessage::Type::CallAborted);
            urc.setHandled(true);
        }
    }
}

A module-services/service-cellular/CellularUrcHandler.hpp => module-services/service-cellular/CellularUrcHandler.hpp +53 -0
@@ 0,0 1,53 @@
// 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 <module-cellular/at/UrcHandler.hpp>

#include <module-cellular/at/UrcClip.hpp>
#include <module-cellular/at/UrcCreg.hpp>
#include <module-cellular/at/UrcCmti.hpp>
#include <module-cellular/at/UrcCusd.hpp>
#include <module-cellular/at/UrcCtze.hpp>
#include <module-cellular/at/UrcQind.hpp>
#include <module-cellular/at/UrcPoweredDown.hpp>
#include <module-cellular/at/UrcResponse.hpp>

using namespace at::urc;

#include "messages/CellularMessage.hpp"
#include "api/CellularServiceAPI.hpp"
#include "ServiceCellular.hpp"

/**
 * ServiceCellular helper for handling Urc messages
 */
class CellularUrcHandler : public UrcHandler
{
  public:
    CellularUrcHandler(ServiceCellular &cellularService) : cellularService(cellularService)
    {}

    void Handle(Clip &urc) final;
    void Handle(Creg &urc) final;
    void Handle(Cmti &urc) final;
    void Handle(Cusd &urc) final;
    void Handle(Ctze &urc) final;
    void Handle(Qind &urc) final;
    void Handle(PoweredDown &urc) final;
    void Handle(UrcResponse &urc) final;

    /**
     * Gets the response that should be returned after handling Urc
     * @return
     */
    std::optional<std::shared_ptr<CellularMessage>> getResponse()
    {
        return std::move(response);
    };

  private:
    ServiceCellular &cellularService;
    std::optional<std::unique_ptr<CellularMessage>> response;
};

M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +17 -140
@@ 2,10 2,12 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <Utils.hpp>       // for removeNewLines, enumToString, split, to_string
#include <at/URC_QIND.hpp> // for QIND
#include <at/URC_CUSD.hpp> // for CUSD
#include <at/URC_CTZE.hpp> // for CTZE
#include <at/URC_CREG.hpp> // for CREG
#include <at/UrcQind.hpp>  // for Qind
#include <at/UrcCusd.hpp>  // for Cusd
#include <at/UrcCtze.hpp>  // for Ctze
#include <at/UrcCreg.hpp>  // for Creg
#include <at/UrcCmti.hpp>  // for Cmti
#include <at/UrcClip.hpp>  // for CLIP
#include <at/response.hpp> // for parseQNWINFO
#include <common_data/EventStore.hpp> // for GSM, GSM::SIM, GSM::SIM::SIM1, GSM::SIM::SIM_FAIL, GSM::SIM::SIM2, GSM::Tray, GSM::Tray::IN, Network
#include <service-evtmgr/Constants.hpp>                                    // for evt_manager


@@ 13,6 15,7 @@
#include <PhoneNumber.hpp>                                                 // for PhoneNumber::View, PhoneNumber
#include <module-db/queries/notifications/QueryNotificationsIncrement.hpp> // for Increment
#include <module-db/queries/messages/sms/QuerySMSSearchByType.hpp>         // for SMSSearchByType, SMSSearchByTypeResult
#include <module-cellular/at/UrcFactory.hpp>                               // for UrcFactory
#include <log/log.hpp>      // for LOG_DEBUG, LOG_ERROR, LOG_INFO, LOG_FATAL, LOG_WARN, LOG_TRACE
#include <bits/exception.h> // for exception
#include <algorithm>        // for remove, max


@@ 28,6 31,7 @@
#include "Service/Service.hpp" // for Service
#include "Service/Timer.hpp"   // for Timer
#include "ServiceCellular.hpp"
#include "CellularUrcHandler.hpp" // for CellularUrcHandler
#include "MessageType.hpp" // for MessageType, MessageType::CellularGetAntenna, MessageType::CellularListCurrentCalls, MessageType::CellularAnswerIncomingCall, MessageType::CellularCall, MessageType::CellularCallRequest, MessageType::CellularGetCREG, MessageType::CellularGetCSQ, MessageType::CellularGetFirmwareVersion, MessageType::CellularGetIMSI, MessageType::CellularGetNWINFO, MessageType::CellularGetNetworkInfo, MessageType::CellularGetOwnNumber, MessageType::CellularGetScanMode, MessageType::CellularGetScanModeResult, MessageType::CellularHangupCall, MessageType::CellularNetworkInfoResult, MessageType::CellularNotification, MessageType::CellularOperatorsScanResult, MessageType::CellularSelectAntenna, MessageType::CellularSetScanMode, MessageType::CellularSimProcedure, MessageType::CellularStartOperatorsScan, MessageType::CellularStateRequest, MessageType::CellularTransmitDtmfTones, MessageType::CellularUSSDRequest, MessageType::DBServiceNotification, MessageType::EVMModemStatus, MessageType::EVMTimeUpdated
#include "messages/CellularMessage.hpp" // for CellularResponseMessage, CellularNotificationMessage, CellularNotificationMessage::Type, CellularCallMessage, CellularUSSDMessage, RawCommandRespAsync, RawCommandResp, CellularUSSDMessage::RequestType, CellularRequestMessage, StateChange, CellularGetChannelMessage, CellularGetChannelResponseMessage, CellularAntennaResponseMessage, CellularCallMessage::Type, CellularTimeNotificationMessage, CellularUSSDMessage::RequestType::abortSesion, CellularAntennaRequestMessage, CellularCallRequestMessage, CellularNotificationMessage::Type::CallActive, RawCommand, CellularCallMessage::Type::IncomingCall, CellularCallMessage::Type::Ringing, CellularDtmfRequestMessage, CellularNotificationMessage::Type::CallAborted, CellularNotificationMessage::Type::NetworkStatusUpdate, CellularNotificationMessage::Type::NewIncomingSMS, CellularNotificationMessage::Type::NewIncomingUSSD, CellularNotificationMessage::Type::PowerDownDeregistered, CellularNotificationMessage::Type::PowerDownDeregistering, CellularNotificationMessage::Type::SIM, CellularNotificationMessage::Type::SignalStrengthUpdate, CellularUSSDMessage::RequestType::pushSesionRequest, CellularNotificationMessage::Type::PowerUpProcedureComplete, CellularNotificationMessage::Type::RawCommand, CellularUSSDMessage::RequestType::pullSesionRequest
#include "SignalStrength.hpp"           // for SignalStrength


@@ 994,38 998,18 @@ sys::Message_t ServiceCellular::DataReceivedHandler(sys::DataMessage *msgl, sys:
        return std::make_shared<sys::ResponseMessage>();
    }
}
namespace
{
    bool isAbortCallNotification(const std::string &str)
    {
        return ((str.find(at::Chanel::NO_CARRIER) != std::string::npos) ||
                (str.find(at::Chanel::BUSY) != std::string::npos) ||
                (str.find(at::Chanel::NO_ANSWER) != std::string::npos));
    }
    namespace powerdown
    {
        static const std::string powerDownNormal = "NORMAL POWER DOWN";
        static const std::string poweredDown     = "POWERED DOWN";

        bool isNormalPowerDown(const std::string str)
        {
            std::string stripped = utils::removeNewLines(str);
            return stripped.find(powerDownNormal) == 0;
        }
        bool isPoweredDown(const std::string str)
        {
            std::string stripped = utils::removeNewLines(str);
            return stripped.find(poweredDown) == 0;
        }
    } // namespace powerdown
} // namespace

std::optional<std::shared_ptr<CellularMessage>> ServiceCellular::identifyNotification(const std::string &data)
{
    std::string str(data.begin(), data.end());
    CellularUrcHandler urcHandler(*this);

    std::string str(data.begin(), data.end());
    std::string logStr = utils::removeNewLines(str);
    LOG_DEBUG("Notification:: %s", logStr.c_str());

    auto urc = at::urc::UrcFactory::Create(str);
    urc->Handle(urcHandler);

    if (auto ret = str.find("+CPIN: ") != std::string::npos) {
        /// TODO handle different sim statuses - i.e. no sim, sim error, sim puk, sim pin etc.
        if (str.find("NOT READY", ret) == std::string::npos) {


@@ 1055,118 1039,11 @@ std::optional<std::shared_ptr<CellularMessage>> ServiceCellular::identifyNotific
        return std::nullopt;
    }

    // Incoming call
    if (auto ret = str.find("+CLIP: ") != std::string::npos) {
        LOG_TRACE("incoming call...");

        auto beg     = str.find("\"", ret);
        auto end     = str.find("\"", ret + beg + 1);
        auto message = str.substr(beg + 1, end - beg - 1);

        return std::make_shared<CellularCallMessage>(CellularCallMessage::Type::IncomingCall, message);
    }

    // Call aborted/failed
    if (isAbortCallNotification(str)) {
        LOG_TRACE("call aborted");
        return std::make_shared<CellularNotificationMessage>(CellularNotificationMessage::Type::CallAborted);
    }

    // Received new SMS
    if (str.find("+CMTI: ") != std::string::npos) {
        LOG_TRACE("received new SMS notification");
        // find message number
        auto tokens = utils::split(str, ',');
        if (tokens.size() == 2) {
            return std::make_shared<CellularNotificationMessage>(CellularNotificationMessage::Type::NewIncomingSMS,
                                                                 tokens[1]);
        }
    }

    // Received signal strength change
    auto qind = at::urc::QIND(str);
    if (qind.is() && qind.isCsq()) {
        AntennaServiceAPI::CSQChange(this);
        auto rssi = qind.getRSSI();
        if (!rssi) {
            LOG_INFO("Invalid csq - ignore");
        }
        else {
            SignalStrength signalStrength(*rssi);

            Store::GSM::get()->setSignalStrength(signalStrength.data);
            return std::make_shared<CellularNotificationMessage>(
                CellularNotificationMessage::Type::SignalStrengthUpdate, str);
        }
    }

    if (str.find("\"FOTA\",\"HTTPEND\",0") != std::string::npos) {
        LOG_DEBUG("Fota UPDATE, switching to AT mode");
        cmux->setMode(TS0710::Mode::AT);
    }

    auto cusd = at::urc::CUSD(str);
    if (cusd.is()) {
        if (cusd.isActionNeeded()) {
            if (ussdState == ussd::State::pullRequestSent) {
                ussdState = ussd::State::pullResponseReceived;
                setUSSDTimer();
            }
        }
        else {
            CellularServiceAPI::USSDRequest(this, CellularUSSDMessage::RequestType::abortSesion);
            ussdState = ussd::State::sesionAborted;
            setUSSDTimer();
        }

        auto message = cusd.getMessage();
        if (!message) {
            return std::nullopt;
        }

        return std::make_shared<CellularNotificationMessage>(CellularNotificationMessage::Type::NewIncomingUSSD,
                                                             *message);
    }
    auto ctze = at::urc::CTZE(str);
    if (ctze.is() && isSettingsAutomaticTimeSyncEnabled()) {
        auto msg = std::make_shared<CellularTimeNotificationMessage>(
            ctze.getGMTTime(), ctze.getTimeZoneOffset(), ctze.getTimeZoneString());
        sys::Bus::SendUnicast(msg, service::name::evt_manager, this);
        return std::nullopt;
    }

    auto creg = at::urc::CREG(str);
    if (creg.is()) {
        if (creg.isValid()) {
            auto accessTechnology = creg.getAccessTechnology();
            auto status           = creg.getStatus();

            LOG_INFO("Network status - %s, access technology %s",
                     utils::enumToString(status).c_str(),
                     utils::enumToString(accessTechnology).c_str());

            Store::Network network{status, accessTechnology};

            Store::GSM::get()->setNetwork(network);
            return std::make_shared<CellularNotificationMessage>(
                CellularNotificationMessage::Type::NetworkStatusUpdate);
        }

        LOG_WARN("Network status - not valid");

        return std::nullopt;
    }

    // Power Down
    if (powerdown::isNormalPowerDown(str)) {
        return std::make_shared<CellularNotificationMessage>(CellularNotificationMessage::Type::PowerDownDeregistering);
    }
    if (powerdown::isPoweredDown(str)) {
        return std::make_shared<CellularNotificationMessage>(CellularNotificationMessage::Type::PowerDownDeregistered);
    if (!urc->isHandled()) {
        LOG_WARN("Unhandled notification: %s", logStr.c_str());
    }

    LOG_WARN("Unhandled notification: %s", logStr.c_str());
    return std::nullopt;
    return urcHandler.getResponse();
}

bool ServiceCellular::sendSMS(SMSRecord record)

M module-services/service-cellular/ServiceCellular.hpp => module-services/service-cellular/ServiceCellular.hpp +2 -0
@@ 171,6 171,8 @@ class ServiceCellular : public sys::Service
    bool handleUSSDRequest(CellularUSSDMessage::RequestType requestType, const std::string &request = "");
    bool handleUSSDURC(void);
    void handleUSSDTimer(void);

    friend class CellularUrcHandler;
};

#endif // PUREPHONE_SERVICECELLULAR_HPP

M module-services/service-fota/CMakeLists.txt => module-services/service-fota/CMakeLists.txt +1 -0
@@ 13,6 13,7 @@ message( "${PROJECT_NAME}  ${CMAKE_CURRENT_SOURCE_DIR}" )
target_sources( ${PROJECT_NAME}
    PRIVATE
        ServiceFota.cpp
        FotaUrcHandler.cpp
        api/FotaServiceAPI.cpp
    PUBLIC
        messages/FotaMessages.hpp

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

#include "FotaUrcHandler.hpp"

void FotaUrcHandler::Handle(Qind &urc)
{
    if (urc.isFotaValid()) {
        if (urc.getFotaStage() == Qind::FotaStage::START) {
            LOG_DEBUG("FOTA UPDATING");
        }
        else if (urc.getFotaStage() == Qind::FotaStage::HTTPEND) {
            LOG_DEBUG("Downloading finished: %s", urc.getFotaParameter().c_str());
        }
        else if (urc.getFotaStage() == Qind::FotaStage::END) {
            LOG_DEBUG("FOTA FINISHED -> reboot (%s)", fotaService.receiverServiceName.c_str());
            fotaService.sendFotaFinshed(fotaService.receiverServiceName);
        }
        else if (urc.getFotaStage() == Qind::FotaStage::UPDATING) {
            auto token_val = 0;
            try {
                token_val = std::stoi(urc.getFotaParameter());
            }
            catch (const std::exception &e) {
                LOG_ERROR("Conversion error of %s, taking default value %d", urc.getFotaParameter().c_str(), token_val);
            }

            unsigned char progress = static_cast<unsigned char>(token_val);
            LOG_DEBUG("FOTA UPDATING: %d", progress);
            fotaService.sendProgress(progress, fotaService.receiverServiceName);
        }
        else if (urc.getFotaStage() == Qind::FotaStage::HTTPSTART) {
            LOG_DEBUG("Start downloading DELTA");
        }
    }
}

A module-services/service-fota/FotaUrcHandler.hpp => module-services/service-fota/FotaUrcHandler.hpp +32 -0
@@ 0,0 1,32 @@
// 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 <module-cellular/at/UrcHandler.hpp>
#include <module-cellular/at/UrcQind.hpp>
#include "ServiceFota.hpp"

using namespace at::urc;

/**
 * ServiceFota helper for handling Urc messages
 */
class FotaUrcHandler : public UrcHandler
{
  public:
    FotaUrcHandler(FotaService::Service &fotaService) : fotaService(fotaService)
    {}

    void Handle(Qind &urc) final;
    virtual void Handle(Clip &urc){};
    virtual void Handle(Creg &urc){};
    virtual void Handle(Cmti &urc){};
    virtual void Handle(Cusd &urc){};
    virtual void Handle(Ctze &urc){};
    virtual void Handle(PoweredDown &urc){};
    virtual void Handle(UrcResponse &urc){};

  private:
    FotaService::Service &fotaService;
};

M module-services/service-fota/ServiceFota.cpp => module-services/service-fota/ServiceFota.cpp +5 -35
@@ 3,8 3,8 @@

#include <Service/Bus.hpp>                             // for Bus
#include <module-cellular/at/Result.hpp>               // for Result, Result::Code, Result::Code::OK
#include <module-cellular/at/URC_QIND.hpp>             // for QIND
#include <service-cellular/api/CellularServiceAPI.hpp> // for GetDataChannel
#include <module-cellular/at/UrcFactory.hpp>           // for UrcFactory
#include <bits/exception.h>                            // for exception
#include <algorithm>                                   // for find_if, remove, transform
#include <cctype>                                      // for tolower


@@ 19,6 19,7 @@
#include "Service/Timer.hpp"      // for Timer
#include "api/FotaServiceAPI.hpp" // for Config, ContextMap, ContextType, HTTPMethod, ContextPair, HTTPMethod::GET, AuthMethod, HTTPErrors, HTTPErrors::OK, HTTPMethod::POST
#include "ServiceFota.hpp"
#include "FotaUrcHandler.hpp"        // FotaUrcHandler
#include "Service/Service.hpp"       // for Service
#include "Service/Message.hpp"       // for Message_t, DataMessage, ResponseMessage
#include "MessageType.hpp"           // for MessageType, MessageType::CellularListCurrentCalls


@@ 579,40 580,9 @@ namespace FotaService

    void Service::parseQIND(const std::string &message)
    {
        auto qind = at::urc::QIND(message);
        if (qind.is()) {
            const unsigned char fotaPrefixTagPosition = 0;
            const unsigned char fotaStatusTagPosition = 1;
            auto tokens                               = qind.getTokens();
            if (tokens[fotaPrefixTagPosition].find("FOTA") != std::string::npos) {
                if (tokens[1].find("START") != std::string::npos) {
                    LOG_DEBUG("FOTA UPDATING");
                }
                else if (tokens[fotaStatusTagPosition].find("HTTPEND") != std::string::npos) {
                    LOG_DEBUG("Downloading finished: %s", tokens[2].c_str());
                }
                else if (tokens[fotaStatusTagPosition].find("END") != std::string::npos) {
                    LOG_DEBUG("FOTA FINISHED -> reboot (%s)", receiverServiceName.c_str());
                    sendFotaFinshed(receiverServiceName);
                }
                else if (tokens[fotaStatusTagPosition].find("UPDATING") != std::string::npos) {
                    auto token_val = 0;
                    try {
                        token_val = std::stoi(tokens[2]);
                    }
                    catch (const std::exception &e) {
                        LOG_ERROR("Conversion error of %s, taking default value %d", tokens[2].c_str(), token_val);
                    }

                    unsigned char progress = static_cast<unsigned char>(token_val);
                    LOG_DEBUG("FOTA UPDATING: %d", progress);
                    sendProgress(progress, receiverServiceName);
                }
                else if (tokens[fotaStatusTagPosition].find("HTTPSTART") != std::string::npos) {
                    LOG_DEBUG("Start downloading DELTA");
                }
            }
        }
        auto urc        = at::urc::UrcFactory::Create(message);
        auto urcHandler = FotaUrcHandler(*this);
        urc->Handle(urcHandler);
    }

    void Service::sendProgress(unsigned int progress, const std::string &receiver)

M module-services/service-fota/ServiceFota.hpp => module-services/service-fota/ServiceFota.hpp +4 -0
@@ 18,6 18,8 @@
#include "Service/Common.hpp"     // for ReturnCodes, ReturnCodes::Success, ServicePowerMode

class DLC_channel;
class FotaUrcHandler;

namespace sys
{
    class Timer;


@@ 113,6 115,8 @@ namespace FotaService
        std::string file;
        std::string receiverServiceName;
        unsigned char currentApnContext = 0;

        friend FotaUrcHandler;
    };

} // namespace FotaService