~aleteoryx/muditaos

091e76ae7c1c70856c3d05ac2cbfa6c99de7b9c1 — Kuba 4 years ago 0079bc9
[EGD-6914] Fix missing no sim message

Added QSIMSTAT urc handler and command parser. Added
sim insertion state in SimCard. Now there No Sim response
is sended if sim is missing. Sim not respond timer removed
from Application Manager.
M module-apps/apps-common/locks/handlers/SimLockHandler.cpp => module-apps/apps-common/locks/handlers/SimLockHandler.cpp +6 -10
@@ 17,17 17,11 @@ namespace locks
    constexpr unsigned int default_attempts           = 4;
    constexpr unsigned int max_input_size             = 8;
    constexpr unsigned int min_input_size             = 4;
    constexpr unsigned int sim_not_responding_timeout = 3;

    SimLockHandler::SimLockHandler(sys::Service *owner)
        : owner(owner), lock(Lock::LockState::Unlocked, default_attempts)
    {
        lock.setInputSizeBounds(min_input_size, max_input_size);

        simResponseTimer = sys::TimerFactory::createSingleShotTimer(
            owner, simResponseTimerName, std::chrono::seconds{sim_not_responding_timeout}, [this](sys::Timer &) {
                handleSimNotRespondingMessage();
            });
    }

    void SimLockHandler::clearStoredInputs()


@@ 38,8 32,6 @@ namespace locks

    void SimLockHandler::setSimInputTypeAction(SimInputTypeAction _simInputTypeAction)
    {
        simResponseTimer.stop();

        if (simInputTypeAction != _simInputTypeAction) {
            simInputTypeAction = _simInputTypeAction;
            lock.lockState     = Lock::LockState::Unlocked;


@@ 107,7 99,6 @@ namespace locks
    void SimLockHandler::setSim(cellular::api::SimSlot simSlot)
    {
        if (simReady) {
            simResponseTimer.start();
            Store::GSM::get()->selected = static_cast<Store::GSM::SIM>(simSlot);
            owner->bus.sendUnicast<cellular::msg::request::sim::SetActiveSim>(simSlot);
        }


@@ 271,10 262,15 @@ namespace locks

    sys::MessagePointer SimLockHandler::handleSimReadyMessage()
    {
        simResponseTimer.stop();
        setSimReady();
        return sys::msgHandled();
    }

    sys::MessagePointer SimLockHandler::handleSimNotInsertedMessage()
    {
        return handleSimNotRespondingMessage();
    }

    sys::MessagePointer SimLockHandler::handleSimNotRespondingMessage()
    {
        setSimInputTypeAction(SimInputTypeAction::Error);

M module-apps/apps-common/locks/handlers/SimLockHandler.hpp => module-apps/apps-common/locks/handlers/SimLockHandler.hpp +1 -3
@@ 13,7 13,6 @@
namespace locks
{
    using StoredLockInput               = std::vector<unsigned int>;
    constexpr auto simResponseTimerName = "SimResponseTimer";

    class SimLockHandler
    {


@@ 28,8 27,6 @@ namespace locks
        StoredLockInput storedFirstInput;
        StoredLockInput storedSecondInput;

        sys::TimerHandle simResponseTimer;

        void clearStoredInputs();
        void setSimInputTypeAction(SimInputTypeAction _simInputTypeAction);



@@ 70,6 67,7 @@ namespace locks
        sys::MessagePointer handleSimPinChangedMessage();
        sys::MessagePointer handleSimAvailabilityMessage();
        sys::MessagePointer handleSimReadyMessage();
        sys::MessagePointer handleSimNotInsertedMessage();
        sys::MessagePointer handleSimNotRespondingMessage();

        void getSettingsSimSelect(const std::string &settingsSim);

M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +2 -0
@@ 24,6 24,7 @@ set(SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcQiurc.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcPoweredDown.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcRing.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcQSimstat.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


@@ 37,6 38,7 @@ set(SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/CPBS.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/CPBR.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/QNWINFO.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/QSIMSTAT.cpp
        )

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

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

namespace at
{
    enum class SimInsertedStatus
    {
        Removed,
        Inserted,
        Unknown
    };

    enum class SimInsertedStatusEnable
    {
        Disable,
        Enable
    };
} // namespace at

M module-cellular/at/UrcHandler.hpp => module-cellular/at/UrcHandler.hpp +2 -0
@@ 16,6 16,7 @@ namespace at::urc
    class Ring;
    class PoweredDown;
    class UrcResponse;
    class QSimstat;

    class UrcHandler
    {


@@ 31,5 32,6 @@ namespace at::urc
        virtual void Handle(Ring &urc)        = 0;
        virtual void Handle(PoweredDown &urc) = 0;
        virtual void Handle(UrcResponse &urc) = 0;
        virtual void Handle(QSimstat &urc)    = 0;
    };
} // namespace at::urc

A module-cellular/at/UrcQSimstat.hpp => module-cellular/at/UrcQSimstat.hpp +40 -0
@@ 0,0 1,40 @@
// 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"
#include "SimInsertedState.hpp"

namespace at::urc
{
    class QSimstat : public Urc
    {
        static constexpr std::string_view head = "+QSIMSTAT";
        const size_t minParametersCount        = 2;

        enum class Tokens
        {
            Enable,
            InsertedStatus
        };

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

        using Urc::Urc;

        [[nodiscard]] auto isValid() const noexcept -> bool override;
        [[nodiscard]] auto getInsertedStatus() const noexcept -> std::optional<at::SimInsertedStatus>;
        [[nodiscard]] auto getEnabled() const noexcept -> std::optional<at::SimInsertedStatusEnable>;

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

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

#pragma once

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

namespace at
{
    namespace result
    {
        struct QSIMSTAT : public Result
        {
            at::SimInsertedStatusEnable enabled;
            at::SimInsertedStatus status;
            explicit QSIMSTAT(const Result &that);
        };
    } // namespace result

    namespace cmd
    {
        class QSIMSTAT : public Cmd
        {
          public:
            QSIMSTAT() noexcept;
            explicit QSIMSTAT(at::cmd::Modifier mod) noexcept;

            [[nodiscard]] auto parseQSIMSTAT(const Result &base_result) -> result::QSIMSTAT;

          private:
            enum class responseTokens
            {
                Enabled,
                SimInserted
            };
            void parseTokens(const std::vector<std::string> &tokens, result::QSIMSTAT &parsed);
        };
    } // namespace cmd

} // namespace at

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

#include <at/cmd/QSIMSTAT.hpp>

#include <log.hpp>
#include <memory>
#include <string>

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

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

        auto QSIMSTAT::parseQSIMSTAT(const Result &base_result) -> result::QSIMSTAT
        {

            auto constexpr responseHeader      = "+QSIMSTAT: ";
            auto constexpr qsimstatTokensCount = 2;
            result::QSIMSTAT parsed{base_result};

            if (parsed) {
                if (parsed.response.empty()) {
                    LOG_ERROR("Can't parse - empty response");
                    parsed.code = result::QSIMSTAT::Code::PARSING_ERROR;
                }
                else {
                    std::string str = parsed.response[0];
                    if (str.find(responseHeader) == std::string::npos) {
                        LOG_ERROR("Can't parse - bad header");
                        parsed.code = result::QSIMSTAT::Code::PARSING_ERROR;
                        return parsed;
                    }

                    utils::findAndReplaceAll(str, responseHeader, "");
                    utils::trim(str);

                    std::vector<std::string> tokens = utils::split(str, ',');

                    if (tokens.size() != qsimstatTokensCount) {
                        LOG_ERROR("Can't parse - invalid tokens count");
                        parsed.code = result::QSIMSTAT::Code::PARSING_ERROR;
                        return parsed;
                    }

                    parseTokens(tokens, parsed);
                }
            }
            return parsed;
        }

        void QSIMSTAT::parseTokens(const std::vector<std::string> &tokens, result::QSIMSTAT &parsed)
        {
            auto status = 0, enabled = 0;
            if (utils::toNumeric(tokens[magic_enum::enum_integer(responseTokens::SimInserted)], status) &&
                utils::toNumeric(tokens[magic_enum::enum_integer(responseTokens::Enabled)], enabled)) {
                if (magic_enum::enum_contains<SimInsertedStatus>(status) &&
                    magic_enum::enum_contains<at::SimInsertedStatusEnable>(enabled)) {
                    parsed.enabled = static_cast<at::SimInsertedStatusEnable>(enabled);
                    parsed.status  = static_cast<SimInsertedStatus>(status);
                    return;
                }
            }

            LOG_ERROR("Can't parse - bad value");
            parsed.code = result::QSIMSTAT::Code::PARSING_ERROR;
        }
    } // namespace cmd
    namespace result
    {
        QSIMSTAT::QSIMSTAT(const Result &that) : Result(that)
        {}

    } // namespace result
} // namespace at

M module-cellular/at/src/UrcFactory.cpp => module-cellular/at/src/UrcFactory.cpp +4 -0
@@ 14,6 14,7 @@
#include <UrcCpin.hpp>
#include <UrcQiurc.hpp>
#include <UrcRing.hpp>
#include <UrcQSimstat.hpp>

using namespace at::urc;



@@ 60,6 61,9 @@ std::unique_ptr<Urc> UrcFactory::Create(const std::string &urcMessage)
    else if (Qiurc::isURC(head)) {
        return std::make_unique<Qiurc>(body);
    }
    else if (QSimstat::isURC(head)) {
        return std::make_unique<QSimstat>(body);
    }

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

A module-cellular/at/src/UrcQSimstat.cpp => module-cellular/at/src/UrcQSimstat.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 "UrcQSimstat.hpp"

using namespace at::urc;

auto QSimstat::isValid() const noexcept -> bool
{
    return tokens.size() >= minParametersCount;
}
auto QSimstat::getInsertedStatus() const noexcept -> std::optional<at::SimInsertedStatus>
{
    auto status = 0;
    if (utils::toNumeric(tokens[magic_enum::enum_integer(Tokens::InsertedStatus)], status) &&
        magic_enum::enum_contains<SimInsertedStatus>(status)) {
        return static_cast<SimInsertedStatus>(status);
    }
    return std::nullopt;
}
auto QSimstat::getEnabled() const noexcept -> std::optional<at::SimInsertedStatusEnable>
{
    auto enabled = 0;
    if (utils::toNumeric(tokens[magic_enum::enum_integer(Tokens::Enable)], enabled) &&
        magic_enum::enum_contains<at::SimInsertedStatusEnable>(enabled)) {
        return static_cast<at::SimInsertedStatusEnable>(enabled);
    }
    return std::nullopt;
}

M module-cellular/test/mock/AtCommon_channel.hpp => module-cellular/test/mock/AtCommon_channel.hpp +80 -0
@@ 429,4 429,84 @@ namespace at
            return result;
        }
    };

    /// provides proper QSIMSTAT response
    class QSIMSTAT_successChannel : public ChannelMock
    {
      public:
        const std::string enabled = "1";
        const std::string status  = "1";

        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+QSIMSTAT: " + enabled + "," + status, "OK"};
            return result;
        }
    };

    /// provides invalid QSIMSTAT response
    class QSIMSTAT_toLittleTokens : public ChannelMock
    {
      public:
        const std::string enabled = "1";

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

    /// provides invalid QSIMSTAT response
    class QSIMSTAT_toManyTokens : public ChannelMock
    {
      public:
        const std::string enabled = "1";
        const std::string status  = "1";
        const std::string bad     = "1";

        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+QSIMSTAT: " + enabled + "," + status + "," + bad, "OK"};
            return result;
        }
    };

    /// provides invalid QSIMSTAT response
    class QSIMSTAT_invalidEnabled : public ChannelMock
    {
      public:
        const std::string enabled = "78";
        const std::string status  = "1";

        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+QSIMSTAT: " + enabled + "," + status, "OK"};
            return result;
        }
    };

    /// provides invalid QSIMSTAT response
    class QSIMSTAT_invalidStatus : public ChannelMock
    {
      public:
        const std::string enabled = "1";
        const std::string status  = "98";

        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+QSIMSTAT: " + enabled + "," + status, "OK"};
            return result;
        }
    };
} // namespace at

M module-cellular/test/unittest_URC.cpp => module-cellular/test/unittest_URC.cpp +55 -0
@@ 22,8 22,10 @@
#include "UrcRing.hpp"
#include "UrcPoweredDown.hpp"
#include "UrcResponse.hpp"
#include <at/UrcQSimstat.hpp>
#include "UrcFactory.hpp"
#include "SimState.hpp"
#include <at/SimInsertedState.hpp>

template <typename urcType> static auto getURC(std::unique_ptr<at::urc::Urc> &urc) -> std::shared_ptr<urcType>
{


@@ 925,3 927,56 @@ TEST_CASE("RING")
        }
    }
}

TEST_CASE("QSimstat")
{
    SECTION("Valid parameter, inserted")
    {
        auto urc      = at::urc::UrcFactory::Create("+QSIMSTAT: 1,1");
        auto qsimstat = getURC<at::urc::QSimstat>(urc);

        REQUIRE(qsimstat);
        REQUIRE(qsimstat->getInsertedStatus() == at::SimInsertedStatus::Inserted);
        REQUIRE(qsimstat->getEnabled() == at::SimInsertedStatusEnable::Enable);
    }

    SECTION("Valid parameter, removed")
    {
        auto urc      = at::urc::UrcFactory::Create("+QSIMSTAT: 1,0");
        auto qsimstat = getURC<at::urc::QSimstat>(urc);

        REQUIRE(qsimstat);
        REQUIRE(qsimstat->getInsertedStatus() == at::SimInsertedStatus::Removed);
        REQUIRE(qsimstat->getEnabled() == at::SimInsertedStatusEnable::Enable);
    }

    SECTION("Valid parameter, unknown")
    {
        auto urc      = at::urc::UrcFactory::Create("+QSIMSTAT: 0,2");
        auto qsimstat = getURC<at::urc::QSimstat>(urc);

        REQUIRE(qsimstat);
        REQUIRE(qsimstat->getInsertedStatus() == at::SimInsertedStatus::Unknown);
        REQUIRE(qsimstat->getEnabled() == at::SimInsertedStatusEnable::Disable);
    }

    SECTION("Invalid parameter, wrong Enable token")
    {
        auto urc      = at::urc::UrcFactory::Create("+QSIMSTAT: 5,2");
        auto qsimstat = getURC<at::urc::QSimstat>(urc);

        REQUIRE(qsimstat);
        REQUIRE(qsimstat->getInsertedStatus() == at::SimInsertedStatus::Unknown);
        REQUIRE(qsimstat->getEnabled() == std::nullopt);
    }

    SECTION("Invalid parameter, wrong Inserted Status token")
    {
        auto urc      = at::urc::UrcFactory::Create("+QSIMSTAT: 0,7");
        auto qsimstat = getURC<at::urc::QSimstat>(urc);

        REQUIRE(qsimstat);
        REQUIRE(qsimstat->getInsertedStatus() == std::nullopt);
        REQUIRE(qsimstat->getEnabled() == at::SimInsertedStatusEnable::Disable);
    }
}

M module-cellular/test/unittest_parse_result.cpp => module-cellular/test/unittest_parse_result.cpp +73 -0
@@ 13,6 13,7 @@
#include <at/cmd/CPBS.hpp>
#include <at/cmd/CPBR.hpp>
#include <at/cmd/QNWINFO.hpp>
#include <at/cmd/QSIMSTAT.hpp>

#include "mock/AtCommon_channel.hpp"
#include "PhoneNumber.hpp"


@@ 681,3 682,75 @@ TEST_CASE("QNWINFO parser test")
        REQUIRE(!resp);
    }
}

TEST_CASE("QSIMSTAT parser")
{
    SECTION("Empty data")
    {
        at::cmd::QSIMSTAT cmd;
        at::Result result;
        auto response = cmd.parseQSIMSTAT(result);
        REQUIRE(!response);
    }

    SECTION("Failing channel")
    {
        at::cmd::QSIMSTAT cmd;
        at::FailingChannel channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQSIMSTAT(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::ERROR);
    }

    SECTION("Success - valid token")
    {
        at::cmd::QSIMSTAT cmd;
        at::QSIMSTAT_successChannel channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQSIMSTAT(base);
        REQUIRE(response);
        REQUIRE(response.enabled == at::SimInsertedStatusEnable::Enable);
        REQUIRE(response.status == at::SimInsertedStatus::Inserted);
    }

    SECTION("to little tokens")
    {
        at::cmd::QSIMSTAT cmd;
        at::QSIMSTAT_toLittleTokens channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQSIMSTAT(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
    }

    SECTION("Failed - to many tokens")
    {
        at::cmd::QSIMSTAT cmd;
        at::QSIMSTAT_toManyTokens channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQSIMSTAT(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
    }

    SECTION("Failed - invalid enabled oken")
    {
        at::cmd::QSIMSTAT cmd;
        at::QSIMSTAT_invalidEnabled channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQSIMSTAT(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
    }

    SECTION("Failed - invalid status token")
    {
        at::cmd::QSIMSTAT cmd;
        at::QSIMSTAT_invalidStatus channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQSIMSTAT(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
    }
}

M module-services/service-cellular/CellularUrcHandler.cpp => module-services/service-cellular/CellularUrcHandler.cpp +11 -0
@@ 210,3 210,14 @@ void CellularUrcHandler::Handle(UrcResponse &urc)
        }
    }
}

void CellularUrcHandler::Handle(QSimstat &urc)
{
    if (urc.isValid()) {
        auto inserted = urc.getInsertedStatus();
        if (inserted.has_value()) {
            response = std::make_unique<cellular::SimInsertedNotication>(inserted.value());
            urc.setHandled(true);
        }
    }
}

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

/**
 * ServiceCellular helper for handling Urc messages


@@ 39,6 40,7 @@ class CellularUrcHandler : public at::urc::UrcHandler
    void Handle(at::urc::Ring &urc) final;
    void Handle(at::urc::PoweredDown &urc) final;
    void Handle(at::urc::UrcResponse &urc) final;
    void Handle(at::urc::QSimstat &urc) final;

    /**
     * Gets the response that should be returned after handling Urc

M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +2 -1
@@ 143,6 143,8 @@ ServiceCellular::ServiceCellular()
                        connectionManager->onTimerTick();
                });
        });
    simTimer = sys::TimerFactory::createSingleShotTimer(
        this, "simTimer", std::chrono::milliseconds{6000}, [this](sys::Timer &) { priv->simCard->handleSimTimer(); });

    ongoingCall.setStartCallAction([=](const CalllogRecord &rec) {
        auto call = DBServiceAPI::CalllogAdd(this, rec);


@@ 1267,7 1269,6 @@ bool ServiceCellular::handle_sim_sanity_check()
    auto ret = sim_check_hot_swap(cmux->get(CellularMux::Channel::Commands));
    if (ret) {
        priv->state->set(State::ST::ModemOn);
        bsp::cellular::sim::simSelect();
    }
    else {
        LOG_ERROR("Sanity check failure - user will be promped about full shutdown");

M module-services/service-cellular/include/service-cellular/api/notification/notification.hpp => module-services/service-cellular/include/service-cellular/api/notification/notification.hpp +3 -0
@@ 45,4 45,7 @@ namespace cellular::msg::notification
        const api::ModemState state;
    };

    struct SimNotInserted : public msg::Notification
    {};

} // namespace cellular::msg::notification

M module-services/service-cellular/service-cellular/CellularMessage.hpp => module-services/service-cellular/service-cellular/CellularMessage.hpp +16 -0
@@ 12,6 12,7 @@
#include <module-bsp/bsp/cellular/bsp_cellular.hpp>
#include <utf8/UTF8.hpp>
#include <SimState.hpp>
#include <module-cellular/at/SimInsertedState.hpp>

#include <response.hpp>



@@ 960,4 961,19 @@ namespace cellular
        std::shared_ptr<std::string> imei;
    };

    class SimInsertedNotication : public sys::DataMessage
    {
      public:
        explicit SimInsertedNotication(at::SimInsertedStatus status)
            : sys::DataMessage(MessageType::MessageTypeUninitialized), insertedStatus(status)
        {}
        auto getInsertedStatus() -> at::SimInsertedStatus
        {
            return insertedStatus;
        }

      private:
        at::SimInsertedStatus insertedStatus;
    };

} // namespace cellular

M module-services/service-cellular/service-cellular/ServiceCellular.hpp => module-services/service-cellular/service-cellular/ServiceCellular.hpp +1 -0
@@ 112,6 112,7 @@ class ServiceCellular : public sys::Service
    sys::TimerHandle callEndedRecentlyTimer;
    sys::TimerHandle stateTimer;
    sys::TimerHandle ussdTimer;
    sys::TimerHandle simTimer;

    // used to enter modem sleep mode
    sys::TimerHandle sleepTimer;

M module-services/service-cellular/src/ServiceCellularPriv.cpp => module-services/service-cellular/src/ServiceCellularPriv.cpp +33 -1
@@ 52,6 52,7 @@ namespace cellular::internal
        simCard->onUnhandledCME = [this](unsigned int code) {
            owner->bus.sendMulticast<notification::UnhandledCME>(code);
        };
        simCard->onSimNotPresent = [this]() { owner->bus.sendMulticast<notification::SimNotInserted>(); };
    }

    void ServiceCellularPriv::connectSimCard()


@@ 62,27 63,49 @@ namespace cellular::internal
         */
        owner->connect(typeid(request::sim::SetActiveSim), [&](sys::Message *request) -> sys::MessagePointer {
            auto msg = static_cast<request::sim::SetActiveSim *>(request);
            return std::make_shared<request::sim::SetActiveSim::Response>(simCard->handleSetActiveSim(msg->sim));
            auto result = simCard->handleSetActiveSim(msg->sim);
            owner->simTimer.start();
            return std::make_shared<request::sim::SetActiveSim::Response>(result);
        });
        owner->connect(typeid(request::sim::GetLockState), [&](sys::Message *) -> sys::MessagePointer {
            if (!simCard->isSimCardInserted()) {
                owner->bus.sendMulticast<notification::SimNotInserted>();
                return sys::MessageNone{};
            }
            return std::make_shared<request::sim::GetLockState::Response>(simCard->handleIsPinLocked());
        });
        owner->connect(typeid(request::sim::ChangePin), [&](sys::Message *request) -> sys::MessagePointer {
            auto msg = static_cast<request::sim::ChangePin *>(request);
            if (!simCard->isSimCardInserted()) {
                owner->bus.sendMulticast<notification::SimNotInserted>();
                return sys::MessageNone{};
            }
            return std::make_shared<request::sim::ChangePin::Response>(simCard->handleChangePin(msg->oldPin, msg->pin));
        });
        owner->connect(typeid(request::sim::UnblockWithPuk), [&](sys::Message *request) -> sys::MessagePointer {
            auto msg = static_cast<request::sim::UnblockWithPuk *>(request);
            if (!simCard->isSimCardInserted()) {
                owner->bus.sendMulticast<notification::SimNotInserted>();
                return sys::MessageNone{};
            }
            return std::make_shared<request::sim::UnblockWithPuk::Response>(
                simCard->handleUnblockWithPuk(msg->puk, msg->pin));
        });
        owner->connect(typeid(request::sim::SetPinLock), [&](sys::Message *request) -> sys::MessagePointer {
            auto msg = static_cast<request::sim::SetPinLock *>(request);
            if (!simCard->isSimCardInserted()) {
                owner->bus.sendMulticast<notification::SimNotInserted>();
                return sys::MessageNone{};
            }
            return std::make_shared<request::sim::SetPinLock::Response>(simCard->handleSetPinLock(msg->pin, msg->lock),
                                                                        msg->lock);
        });
        owner->connect(typeid(request::sim::PinUnlock), [&](sys::Message *request) -> sys::MessagePointer {
            auto msg = static_cast<request::sim::PinUnlock *>(request);
            if (!simCard->isSimCardInserted()) {
                owner->bus.sendMulticast<notification::SimNotInserted>();
                return sys::MessageNone{};
            }
            return std::make_shared<request::sim::PinUnlock::Response>(simCard->handlePinUnlock(msg->pin));
        });



@@ 93,6 116,15 @@ namespace cellular::internal
            simCard->handleTrayState();
            return sys::MessageNone{};
        });
        owner->connect(typeid(cellular::SimInsertedNotication), [&](sys::Message *request) -> sys::MessagePointer {
            auto message = static_cast<cellular::SimInsertedNotication *>(request);

            if (simCard->isSimSelectInProgress()) {
                return sys::MessageNone{};
            }
            simCard->handleSimInsertionNotification(message->getInsertedStatus());
            return sys::MessageNone{};
        });

        /**
         * Internal message handlers

M module-services/service-cellular/src/SimCard.cpp => module-services/service-cellular/src/SimCard.cpp +50 -0
@@ 9,6 9,7 @@
#include <at/ATFactory.hpp>
#include <at/UrcFactory.hpp>
#include <at/UrcCpin.hpp>
#include <at/cmd/QSIMSTAT.hpp>

namespace cellular
{


@@ 63,6 64,8 @@ namespace cellular
            Store::GSM::get()->selected = static_cast<Store::GSM::SIM>(sim);
            bsp::cellular::sim::simSelect();
            bsp::cellular::sim::hotSwapTrigger();
            clearSimInsertedStatus();
            simSelectInProgress = true;
            return true;
        }



@@ 302,5 305,52 @@ namespace cellular

            return sim::Result::OK;
        }

        bool SimCard::isSimCardInserted()
        {
            if (simInserted == std::nullopt) {
                if (simInserted = readSimCardInsertStatus(); !simInserted) {
                    return false;
                }
            }

            if (simInserted == at::SimInsertedStatus::Inserted || simInserted == at::SimInsertedStatus::Unknown) {
                return true;
            }
            return false;
        }

        std::optional<at::SimInsertedStatus> SimCard::readSimCardInsertStatus()
        {
            auto command  = at::cmd::QSIMSTAT(at::cmd::Modifier::Get);
            auto response = channel->cmd(command);
            auto result   = command.parseQSIMSTAT(response);

            if (result.code != at::Result::Code::OK) {
                LOG_ERROR("Can't read SIM insertion status.");
                return std::nullopt;
            }
            return result.status;
        }
        void SimCard::handleSimTimer()
        {
            simSelectInProgress = false;
            if (!isSimCardInserted()) {
                if (onSimNotPresent) {
                    onSimNotPresent();
                }
            }
        }
        void SimCard::handleSimInsertionNotification(at::SimInsertedStatus status)
        {
            if (auto actual = getSimInsertedStatus(); actual.has_value() && actual != status) {
                setSimInserted(status);
                if (status == at::SimInsertedStatus::Removed) {
                    if (onSimNotPresent) {
                        onSimNotPresent();
                    }
                }
            }
        }
    } // namespace service
} // namespace cellular

M module-services/service-cellular/src/SimCard.hpp => module-services/service-cellular/src/SimCard.hpp +72 -0
@@ 7,6 7,8 @@
#include <at/SimState.hpp>
#include <service-cellular/api/common.hpp>

#include <module-cellular/at/SimInsertedState.hpp>

namespace at
{
    class Cmd;


@@ 99,6 101,67 @@ namespace cellular::service
        void handleATSimStateChange(at::SimState state);

        /**
         * Check if sim card is present in slot
         * @return true if sim card is present in slot
         */
        bool isSimCardInserted();

        /**
         * Set new sim inserted status
         * @param newStatus
         */
        void setSimInserted(at::SimInsertedStatus newStatus)
        {
            simInserted = newStatus;
        }

        /**
         * Gets sim inserted status
         * @return actual value of sim inserted status
         */
        std::optional<at::SimInsertedStatus> getSimInsertedStatus()
        {
            return simInserted;
        }

        /**
         * Clears sim inserted status
         */
        void clearSimInsertedStatus()
        {
            simInserted = std::nullopt;
        }

        /**
         * Gets Sim Select progress state
         * @return true if sim selecting is in progres
         */
        bool isSimSelectInProgress()
        {
            return simSelectInProgress;
        }

        /**
         * Sets Sim Select progress state
         * @param inProgress new progress state
         */
        void setSimSelectInProgress(bool inProgress)
        {
            simSelectInProgress = inProgress;
        }

        /**
         * Sim timer event handler
         */
        void handleSimTimer();

        /**
         * Sim Inserted notification handler
         * @param status new Sim Inserted status
         */
        void handleSimInsertionNotification(at::SimInsertedStatus status);

        /**
         * Notification events
         */
        std::function<void()> onSimReady;


@@ 107,6 170,7 @@ namespace cellular::service
        std::function<void()> onSimBlocked;
        std::function<void()> onSimEvent;
        std::function<void(unsigned int code)> onUnhandledCME;
        std::function<void()> onSimNotPresent;

      private:
        /** SIM card initialization sequence


@@ 168,8 232,16 @@ namespace cellular::service

        void handleSimState(at::SimState state);

        /**
         * Read sim card insert status
         * @return sim card inserted status
         */
        std::optional<at::SimInsertedStatus> readSimCardInsertStatus();

        at::BaseChannel *channel        = nullptr;
        std::optional<api::SimSlot> sim = std::nullopt;
        std::optional<at::SimInsertedStatus> simInserted = std::nullopt;
        bool simSelectInProgress                         = false;
    };

} // namespace cellular::service

M module-services/service-fota/FotaUrcHandler.hpp => module-services/service-fota/FotaUrcHandler.hpp +1 -0
@@ 28,6 28,7 @@ class FotaUrcHandler : public at::urc::UrcHandler
    virtual void Handle(at::urc::Ring &urc){};
    virtual void Handle(at::urc::PoweredDown &urc){};
    virtual void Handle(at::urc::UrcResponse &urc){};
    virtual void Handle(at::urc::QSimstat &urc){};

  private:
    FotaService::Service &fotaService;

M products/PurePhone/services/appmgr/ApplicationManager.cpp => products/PurePhone/services/appmgr/ApplicationManager.cpp +3 -0
@@ 296,6 296,9 @@ namespace app::manager
            }
            return simLockHandler.handleCMEErrorRequest(data->code);
        });
        connect(typeid(cellular::msg::notification::SimNotInserted), [&](sys::Message *request) -> sys::MessagePointer {
            return simLockHandler.handleSimNotInsertedMessage();
        });
        connect(typeid(locks::SetSim), [&](sys::Message *request) -> sys::MessagePointer {
            auto data = static_cast<locks::SetSim *>(request);
            simLockHandler.setSim(data->getSimSlot());