~aleteoryx/muditaos

39a09e862722d241acd1897ed913b710b198002b — Wiktor S. Ovalle Correa 4 years ago 5e8f773
[EGD-6746] New SimCard implementation

New approach to the Cellular SimCard submodule
M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +7 -0
@@ 2,6 2,10 @@ project(service-cellular)
message( "${PROJECT_NAME}  ${CMAKE_CURRENT_LIST_DIR}" )

set(SOURCES
    src/ServiceCellularPriv.cpp
    src/state.cpp
    src/SimCard.cpp

    CellularCall.cpp
    CellularServiceAPI.cpp
    CellularUrcHandler.cpp


@@ 37,6 41,9 @@ add_library(${PROJECT_NAME} STATIC ${SOURCES})
target_include_directories(${PROJECT_NAME}
    PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}
        ${CMAKE_CURRENT_LIST_DIR}/include
    PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}/src
)

target_link_libraries(${PROJECT_NAME}

A module-services/service-cellular/include/service-cellular-api => module-services/service-cellular/include/service-cellular-api +7 -0
@@ 0,0 1,7 @@
// 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 "service-cellular/api/request/sim.hpp"
#include "service-cellular/api/notification/notification.hpp"

A module-services/service-cellular/include/service-cellular/api/common.hpp => module-services/service-cellular/include/service-cellular/api/common.hpp +37 -0
@@ 0,0 1,37 @@
// 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 <vector>

namespace cellular
{
    namespace service
    {
        constexpr const char *name = "ServiceCellular";
    }

    namespace api
    {
        enum class CallMode
        {
            Regular,
            Emergency
        };

        enum class SimLockState
        {
            Locked,
            Unlocked
        };

        enum class SimSlot
        {
            SIM1 = 0,
            SIM2 = 1
        };

        using SimCode = std::vector<unsigned int>;
    } // namespace api
} // namespace cellular

A module-services/service-cellular/include/service-cellular/api/notification/notification.hpp => module-services/service-cellular/include/service-cellular/api/notification/notification.hpp +51 -0
@@ 0,0 1,51 @@
// 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 <Service/Message.hpp>

#include <service-cellular/api/common.hpp>

namespace cellular::msg
{
    struct Notification : public sys::Message
    {
        Notification() : Message(Type::Data)
        {}
    };

    namespace notification
    {
        struct SimReady : public msg::Notification
        {
            explicit SimReady(bool ready) : ready(ready)
            {}
            const bool ready;
        };

        struct SimNeedPin : public msg::Notification
        {
            explicit SimNeedPin(unsigned int attempts) : attempts(attempts)
            {}
            const unsigned int attempts;
        };

        struct SimNeedPuk : public msg::Notification
        {
            explicit SimNeedPuk(unsigned int attempts) : attempts(attempts)
            {}
            const unsigned int attempts;
        };

        struct SimBlocked : public msg::Notification
        {};

        struct UnhandledCME : public msg::Notification
        {
            explicit UnhandledCME(unsigned int code) : code(code)
            {}
            const unsigned int code;
        };
    } // namespace notification
} // namespace cellular::msg

A module-services/service-cellular/include/service-cellular/api/request/request.hpp => module-services/service-cellular/include/service-cellular/api/request/request.hpp +25 -0
@@ 0,0 1,25 @@
// 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 <Service/Message.hpp>

#include <service-cellular/api/common.hpp>

namespace cellular::msg
{
    struct Request : public sys::Message
    {
        Request() : Message(Type::Data)
        {}
        static constexpr const char *target = cellular::service::name;
    };
    struct Response : public sys::Message
    {
        explicit Response(bool retCode = true) : Message(Type::Data), retCode(retCode)
        {}
        const bool retCode;
    };

} // namespace cellular::msg

A module-services/service-cellular/include/service-cellular/api/request/sim.hpp => module-services/service-cellular/include/service-cellular/api/request/sim.hpp +84 -0
@@ 0,0 1,84 @@
// 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 "request.hpp"

namespace cellular::msg::request::sim
{
    /** Set active SIM card
     */
    struct SetActiveSim : public msg::Request
    {
        explicit SetActiveSim(api::SimSlot sim) : sim(sim)
        {}
        const api::SimSlot sim;

        using Response = msg::Response;
    };

    /** Get current state of PIN lock
     */
    struct GetLockState : public msg::Request
    {
        struct Response : public msg::Response
        {
            explicit Response(bool locked) : locked(locked)
            {}
            const bool locked;
        };
    };

    /** Unlock SIM using PIN
     */
    struct PinUnlock : msg::Request
    {
        explicit PinUnlock(const api::SimCode &pin) : pin(pin)
        {}
        const api::SimCode pin;

        using Response = msg::Response;
    };

    /** Change current PIN
     */
    struct ChangePin : msg::Request
    {
        explicit ChangePin(const api::SimCode &oldPin, const api::SimCode &pin) : oldPin(oldPin), pin(pin)
        {}
        const api::SimCode oldPin;
        const api::SimCode pin;

        using Response = msg::Response;
    };

    /** Reset PIN using PUK
     */
    struct UnblockWithPuk : msg::Request
    {
        explicit UnblockWithPuk(const api::SimCode &puk, const api::SimCode &pin) : puk(puk), pin(pin)
        {}
        const api::SimCode puk;
        const api::SimCode pin;

        using Response = msg::Response;
    };

    /** Enable or disable PIN lock on SIM
     */
    struct SetPinLock : msg::Request
    {
        explicit SetPinLock(api::SimLockState lock, const api::SimCode &pin) : lock(lock), pin(pin)
        {}
        const api::SimLockState lock;
        const api::SimCode pin;

        struct Response : msg::Response
        {
            explicit Response(bool retCode, api::SimLockState lock) : msg::Response(retCode), lock(lock)
            {}
            const api::SimLockState lock;
        };
    };
} // namespace cellular::msg::request::sim

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

#pragma once

namespace sys
{
    class Service;
}

namespace cellular::service
{
    class State
    {
      public:
        enum class ST
        {
            Idle,                        /// does nothing
            WaitForStartPermission,      /// waiting for permission to start the module
            PowerUpRequest,              /// process request of power up
            StatusCheck,                 /// set on service start - check for modem status - skipped on T3 board
            PowerUpProcedure,            /// due to lack of Status pin on T3, we don't know whether is on or off
            PowerUpInProgress,           /// waiting for modem powered up by polling various bauds
            BaudDetect,                  /// baud detection procedure
            CellularConfProcedure,       /// configuration procedure
            AudioConfigurationProcedure, /// audio configuration for modem (could be in ModemConfiguration)
            APNConfProcedure,            /// Configure APN set by user, check if modem have similar
            SanityCheck,                 /// prior to ModemOn last sanity checks for one time configurations etc
            ModemOn,   /// modem ready - indicates that modem is fully configured, ( **SIM is not yet configured** )
            URCReady,  /// State indicates that URC handling is enabled
            SimInit,   /// initialize sim card
            SimSelect, /// triggers hw SIM selection (! state now will be **changed on URC** )
            ModemFatalFailure, /// modem full shutdown need
            Failed,
            Ready,            /// Service is fully initialized
            PowerDownStarted, /// modem is disconnecting from network. It might take a while if poor coverage
            PowerDownWaiting, /// modem has deregistered and is now approaching low power state
            PowerDown,        /// modem is known to be turned off
        };

        enum class PowerState
        {
            Off,
            On
        };

      private:
        enum ST state = ST::Idle;
        sys::Service *owner;

      public:
        explicit State(sys::Service *owner);

        [[nodiscard]] static const char *c_str(ST state);
        [[nodiscard]] const char *c_str() const;

        /// 1. sets state of ServiceCellular
        /// 2. sends Multicast notification of ServiceCellular state
        ///
        /// \note This is for service cellular only it could be private and friend
        void set(ST state);

        ST get() const;
    };
} // namespace cellular::service

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

#include "ServiceCellularPriv.hpp"
#include "messages.hpp"

#include <service-cellular-api>

#include <service-evtmgr/EVMessages.hpp>
#include <service-evtmgr/Constants.hpp>

namespace cellular::internal
{
    ServiceCellularPriv::ServiceCellularPriv(ServiceCellular *owner)
        : owner{owner}, simCard{std::make_unique<SimCard>()}, state{std::make_unique<State>(owner)}
    {
        initSimCard();
    }

    void ServiceCellularPriv::initSimCard()
    {
        using namespace cellular::msg;
        simCard->onSimReady = [this](bool ready) {
            // SIM causes SIM INIT, only on ready
            if (ready) {
                state->set(State::ST::SimInit);
            }
            owner->bus.sendMulticast(std::make_shared<notification::SimReady>(ready),
                                     sys::BusChannel::ServiceCellularNotifications);
        };
        simCard->onNeedPin = [this](unsigned int attempts) {
            owner->bus.sendMulticast(std::make_shared<notification::SimNeedPin>(attempts),
                                     sys::BusChannel::ServiceCellularNotifications);
        };
        simCard->onNeedPuk = [this](unsigned int attempts) {
            owner->bus.sendMulticast(std::make_shared<notification::SimNeedPuk>(attempts),
                                     sys::BusChannel::ServiceCellularNotifications);
        };
        simCard->onSimBlocked = [this]() {
            owner->bus.sendMulticast(std::make_shared<notification::SimBlocked>(),
                                     sys::BusChannel::ServiceCellularNotifications);
        };
        simCard->onSimEvent = [this]() {
            owner->bus.sendUnicast(std::make_shared<sevm::SIMMessage>(), ::service::name::evt_manager);
        };
        simCard->onUnhandledCME = [this](unsigned int code) {
            owner->bus.sendMulticast(std::make_shared<notification::UnhandledCME>(code),
                                     sys::BusChannel::ServiceCellularNotifications);
        };
    }

    void ServiceCellularPriv::connectSimCard()
    {
        using namespace cellular::msg;
        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));
        });
        owner->connect(typeid(request::sim::GetLockState), [&](sys::Message *) -> sys::MessagePointer {
            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);
            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);
            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);
            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);
            return std::make_shared<request::sim::PinUnlock::Response>(simCard->handlePinUnlock(msg->pin));
        });
        owner->connect(typeid(internal::msg::SimStateChanged), [&](sys::Message *request) -> sys::MessagePointer {
            auto msg = static_cast<internal::msg::SimStateChanged *>(request);
            simCard->handleSimStateChanged(msg->state);
            return sys::MessageNone{};
        });
    }

} // namespace cellular::internal

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

#pragma once

#include <service-cellular/ServiceCellular.hpp>
#include <service-cellular/state.hpp>

#include "SimCard.hpp"

namespace cellular::internal
{
    using service::SimCard;
    using service::State;

    class ServiceCellularPriv
    {
        ServiceCellular *owner;

        std::unique_ptr<SimCard> simCard;
        std::unique_ptr<State> state;

        State::PowerState nextPowerState = State::PowerState::Off;

      public:
        ServiceCellularPriv(ServiceCellular *owner);

        void connectSimCard();

      private:
        void initSimCard();
    };
} // namespace cellular::internal

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

#include "SimCard.hpp"

#include <common_data/EventStore.hpp>
#include <bsp/cellular/bsp_cellular.hpp>
#include <modem/BaseChannel.hpp>
#include <at/ATFactory.hpp>
#include <at/UrcFactory.hpp>
#include <at/UrcCpin.hpp>

namespace cellular
{
    namespace internal
    {
        constexpr const char *pinString[] = {"SC", "P2"};

        service::sim::Result convertErrorFromATResult(const at::Result atres)
        {
            if (std::holds_alternative<at::EquipmentErrorCode>(atres.errorCode)) {

                auto err = static_cast<int>(std::get<at::EquipmentErrorCode>(atres.errorCode));
                if ((err > static_cast<int>(service::sim::Result::AT_ERROR_Begin)) &&
                    (err < static_cast<int>(service::sim::Result::AT_ERROR_End))) {
                    return static_cast<service::sim::Result>(err);
                }
            }
            return service::sim::Result::Unknown;
        }

        std::string simCodeToString(const cellular::api::SimCode &v)
        {
            std::string buf;
            std::transform(v.begin(), v.end(), std::back_inserter(buf), [](auto &&c) { return '0' + c; });
            return buf;
        }

    } // namespace internal

    namespace service
    {
        bool SimCard::ready() const
        {
            return channel;
        }

        void SimCard::setChannel(at::BaseChannel *channel)
        {
            this->channel = channel;
        }

        bool SimCard::handleSetActiveSim(api::SimSlot sim)
        {
            Store::GSM::get()->selected = static_cast<Store::GSM::SIM>(sim);
            bsp::cellular::sim::simSelect();
            bsp::cellular::sim::hotSwapTrigger();
            return true;
        }

        bool SimCard::handleIsPinLocked() const
        {
            return isPinLocked();
        }

        bool SimCard::handleChangePin(const api::SimCode &oldPin, const api::SimCode &pin)
        {
            const auto _oldPin = internal::simCodeToString(oldPin);
            const auto _pin    = internal::simCodeToString(pin);
            return processPinResult(changePin(_oldPin, _pin));
        }

        bool SimCard::handleUnblockWithPuk(const api::SimCode &puk, const api::SimCode &pin)
        {
            const auto _puk = internal::simCodeToString(puk);
            const auto _pin = internal::simCodeToString(pin);
            return processPinResult(supplyPuk(_puk, _pin));
        }

        bool SimCard::handleSetPinLock(const api::SimCode &pin, api::SimLockState lock)
        {
            const auto _pin = internal::simCodeToString(pin);
            return processPinResult(setPinLock(_pin, lock == cellular::api::SimLockState::Locked));
        }

        bool SimCard::handlePinUnlock(const api::SimCode &pin)
        {
            const auto _pin = internal::simCodeToString(pin);
            return processPinResult(supplyPin(_pin));
        }

        void SimCard::handleSimStateChanged(at::SimState state)
        {
            handleSimState(state);
        }

        void SimCard::handleSimState(at::SimState state)
        {
            switch (state) {
            case at::SimState::Ready:
                Store::GSM::get()->sim = Store::GSM::get()->selected;
                if (onSimReady)
                    onSimReady(true);
                break;
            case at::SimState::NotReady:
                Store::GSM::get()->sim = Store::GSM::SIM::SIM_FAIL;
                if (onSimReady)
                    onSimReady(false);
                break;
            case at::SimState::SimPin:
                [[fallthrough]];
            case at::SimState::SimPin2: {
                if (auto pc = getAttemptsCounters(state == at::SimState::SimPin ? sim::Pin::PIN1 : sim::Pin::PIN2);
                    pc) {
                    if (pc.value().PukCounter != 0) {
                        if (onNeedPin)
                            onNeedPin(pc.value().PinCounter);
                        break;
                    }
                }
                if (onSimBlocked)
                    onSimBlocked();
                break;
            }
            case at::SimState::SimPuk:
                [[fallthrough]];
            case at::SimState::SimPuk2: {
                if (auto pc = getAttemptsCounters(state == at::SimState::SimPuk ? sim::Pin::PIN1 : sim::Pin::PIN2);
                    pc) {
                    if (pc.value().PukCounter != 0) {
                        if (onNeedPuk)
                            onNeedPuk(pc.value().PukCounter);
                        break;
                    }
                }
                if (onSimBlocked)
                    onSimBlocked();
                break;
            }
            case at::SimState::Locked:
                Store::GSM::get()->sim = Store::GSM::SIM::SIM_FAIL;
                if (onSimBlocked)
                    onSimBlocked();
                break;
            case at::SimState::PhNetPin:
                [[fallthrough]];
            case at::SimState::PhNetPuk:
                [[fallthrough]];
            case at::SimState::PhNetSPin:
                [[fallthrough]];
            case at::SimState::PhNetSPuk:
                [[fallthrough]];
            case at::SimState::PhSpPin:
                [[fallthrough]];
            case at::SimState::PhSpPuk:
                [[fallthrough]];
            case at::SimState::PhCorpPin:
                [[fallthrough]];
            case at::SimState::PhCorpPuk:
                [[fallthrough]];
            case at::SimState::Unknown:
                LOG_ERROR("SimState not supported");
                Store::GSM::get()->sim = Store::GSM::SIM::SIM_UNKNOWN;
                break;
            }
            if (onSimEvent)
                onSimEvent();
        }

        std::optional<at::response::qpinc::AttemptsCounters> SimCard::getAttemptsCounters(sim::Pin pin) const
        {
            if (!ready()) {
                return std::nullopt;
            }

            auto resp =
                channel->cmd(at::factory(at::AT::QPINC) + "\"" + internal::pinString[static_cast<int>(pin)] + "\"");
            at::response::qpinc::AttemptsCounters ret;
            if (at::response::parseQPINC(resp, ret)) {
                return ret;
            }

            return std::nullopt;
        }

        bool SimCard::processPinResult(sim::Result result)
        {
            if (result == sim::Result::IncorrectPassword) {
                if (auto state = simState(); state) {
                    handleSimState(*state);
                }
            }
            else if (result != sim::Result::OK) {
                if (onUnhandledCME)
                    onUnhandledCME(static_cast<unsigned int>(result));
            }
            return result == sim::Result::OK;
        }

        sim::Result SimCard::supplyPin(const std::string &pin) const
        {
            return sendCommand(sim::LockType::PIN, at::factory(at::AT::CPIN) + "\"" + pin + "\"");
        }

        sim::Result SimCard::changePin(const std::string &oldPin, const std::string &newPin) const
        {
            return sendCommand(sim::LockType::PIN,
                               at::factory(at::AT::CPWD) + "\"SC\", \"" + oldPin + "\",\"" + newPin + "\"");
        }

        sim::Result SimCard::supplyPuk(const std::string &puk, const std::string &pin) const
        {
            return sendCommand(sim::LockType::PUK, at::factory(at::AT::CPIN) + "\"" + puk + "\"" + ",\"" + pin + "\"");
        }

        sim::Result SimCard::setPinLock(const std::string &pin, bool lock) const
        {
            return sendCommand(sim::LockType::PIN,
                               at::factory(at::AT::CLCK) + "\"SC\"," + (lock ? "1" : "0") + ",\"" + pin + "\"");
        }

        bool SimCard::isPinLocked() const
        {
            auto resp = channel->cmd(at::factory(at::AT::CLCK) + "\"SC\",2\r");
            int val   = 0;
            if (at::response::parseCLCK(resp, val)) {
                return val != 0;
            }
            return true;
        }

        std::optional<at::SimState> SimCard::simState() const
        {
            auto resp = channel->cmd(at::factory(at::AT::GET_CPIN));
            if (resp.code == at::Result::Code::OK) {
                if (resp.response.size()) {
                    for (auto el : resp.response) {
                        auto urc = at::urc::UrcFactory::Create(el);
                        if (auto cpin = dynamic_cast<at::urc::Cpin *>(urc.get())) {
                            return cpin->getState();
                        }
                    }
                }
            }
            return at::SimState::Unknown;
        }

        sim::Result SimCard::sendCommand(sim::LockType check, const at::Cmd &cmd) const
        {
            if (auto pc = getAttemptsCounters(); pc) {
                switch (check) {
                case sim::LockType::PIN:
                    if (pc.value().PinCounter == 0)
                        return sim::Result::Locked;
                    break;
                case sim::LockType::PUK:
                    if (pc.value().PukCounter == 0)
                        return sim::Result::Locked;
                    break;
                }
            }
            else {
                return sim::Result::Unknown;
            }

            if (auto resp = channel->cmd(cmd); resp.code != at::Result::Code::OK) {
                return internal::convertErrorFromATResult(resp);
            }

            return sim::Result::OK;
        }
    } // namespace service
} // namespace cellular

A module-services/service-cellular/src/SimCard.hpp => module-services/service-cellular/src/SimCard.hpp +160 -0
@@ 0,0 1,160 @@
// 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 <at/response.hpp>
#include <at/SimState.hpp>
#include <service-cellular/api/common.hpp>

namespace at
{
    class Cmd;
    class BaseChannel;
} // namespace at

namespace cellular::service::sim
{
    enum class Pin
    {
        PIN1 = 0,
        PIN2 = 1
    };

    enum class Result
    {
        OK     = 0,
        Ready  = 1,
        Locked = 2, /*!< In case of attempt counters set to 0 */

        AT_ERROR_Begin = 9, /*!< this is only for separate AT SIM errors from new one added, AT errors list end with
                               AT_ERROR_End */

        SIMNotInserted    = 10,
        SIM_PIN_required  = 11,
        SIM_PUKRequired   = 12,
        Failure           = 13,
        Busy              = 14,
        Wrong             = 15,
        IncorrectPassword = 16,

        AT_ERROR_End = 17,

        Unknown = 0xFF /*!< Unknown, any reason (not only AT), in some case AT commends return just error, eg. twice
                                  supply good pin, second AT commend return ERROR */

    };

    enum class LockType
    {
        PIN,
        PUK
    };
} // namespace cellular::service::sim

namespace cellular::internal
{
    service::sim::Result convertErrorFromATResult(const at::Result atres);
    std::string simCodeToString(const cellular::api::SimCode &v);
} // namespace cellular::internal

namespace cellular::service
{
    class SimCard
    {
      public:
        /** Check if sim card slot has been selected and cmd channel is set
         * @return true if ready to communicate
         */
        bool ready() const;

        /** Set cmd channel
         * \param channel channel (or nullptr to block communication)
         */
        void setChannel(at::BaseChannel *channel = nullptr);

        /**
         * Request message handlers
         */
        bool handleSetActiveSim(api::SimSlot sim);
        bool handleIsPinLocked() const;
        bool handleChangePin(const api::SimCode &old_pin, const api::SimCode &pin);
        bool handleUnblockWithPuk(const api::SimCode &puk, const api::SimCode &pin);
        bool handleSetPinLock(const api::SimCode &pin, api::SimLockState lock);
        bool handlePinUnlock(const api::SimCode &pin);

        /**
         * Internal message handlers
         */
        void handleSimStateChanged(at::SimState state);

        /**
         * Notification events
         */
        std::function<void(bool ready)> onSimReady;
        std::function<void(unsigned int attempts)> onNeedPin;
        std::function<void(unsigned int attempts)> onNeedPuk;
        std::function<void()> onSimBlocked;
        std::function<void()> onSimEvent;
        std::function<void(unsigned int code)> onUnhandledCME;

      private:
        /** Get information about attempts of PIN and PUK for standard sim card (optionally PIN2/PUK2)
         * @return  As optional SimCard::AttemptsCounters, in case of error nullopt. Should be noted that in some case
         * could return SIMFailure which could mean 0 attempts (happen if lock during session, on modem/sim reboot again
         * return 0,0);
         */
        std::optional<at::response::qpinc::AttemptsCounters> getAttemptsCounters(sim::Pin pin = sim::Pin::PIN1) const;

        /** Supply pin for modem
         * \param pin digits as a string from 4-8 digits
         * \return return OK on success in other case see details in SimCardResult
         */
        sim::Result supplyPin(const std::string &pin) const;

        /** Supply pin for modem
         * \param puk puk as standard 8 digits
         * \param pin, new pin digits as a string from 4-8 digits
         * \return return OK on success in other case see details in SimCardResult
         */
        sim::Result supplyPuk(const std::string &puk, const std::string &pin) const;

        /** Set whether to provide pin. Always need to provide actual pin for sim card, only for standard PIN
         * \param lock true for lock SIM card
         * \param pin actual pin for SIM card
         * \return
         */
        sim::Result setPinLock(const std::string &pin, bool lock) const;

        /** Change pin, only for standard pin. To get effect of change pin, SIM cart or modem should be restarted
         * simplest solution is to call AT+CFUN=0/1
         * \param oldPin
         * \param newPin
         * \return return OK on success, else see SimCardResult
         */
        sim::Result changePin(const std::string &oldPin, const std::string &newPin) const;

        /** Check whether the pin needs to be provided, only for standard pin.
         * \return true if need pin to unlock SIM card functionality
         */
        bool isPinLocked() const;

        /** Read internal SIM state using CPIN AT commands
         */
        std::optional<at::SimState> simState() const;

        /** Process sim::Result from PIN lock/unlock operations
         * \param result result from operation (`sendCommand()`)
         * \result return true on success
         */
        bool processPinResult(sim::Result result);

        sim::Result sendCommand(sim::LockType check, const at::Cmd &cmd) const;

        void handleSimState(at::SimState state);

        at::BaseChannel *channel        = nullptr;
        std::optional<api::SimSlot> sim = std::nullopt;
    };

} // namespace cellular::service

A module-services/service-cellular/src/messages.hpp => module-services/service-cellular/src/messages.hpp +17 -0
@@ 0,0 1,17 @@
// 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 <service-cellular/api/request/request.hpp>
#include <at/SimState.hpp>

namespace cellular::internal::msg
{
    struct SimStateChanged : public cellular::msg::Request
    {
        SimStateChanged(at::SimState state) : state(state)
        {}
        at::SimState state;
    };
} // namespace cellular::internal::msg

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

#include <service-cellular/state.hpp>
#include <Service/Service.hpp>
#include <service-cellular/CellularMessage.hpp>

namespace cellular::service
{
    const char *State::c_str(State::ST state)
    {
        switch (state) {
        case ST::Idle:
            return "Idle";
        case ST::WaitForStartPermission:
            return "WaitForStartPermission";
        case ST::PowerUpRequest:
            return "PowerUpRequest";
        case ST::StatusCheck:
            return "StatusCheck";
        case ST::PowerUpInProgress:
            return "PowerUpInProgress";
        case ST::PowerUpProcedure:
            return "PowerUpProcedure";
        case ST::BaudDetect:
            return "BaudDetect";
        case ST::AudioConfigurationProcedure:
            return "AudioConfigurationProcedure";
        case ST::APNConfProcedure:
            return "APNConfProcedure";
        case ST::ModemOn:
            return "ModemOn";
        case ST::URCReady:
            return "URCReady";
        case ST::SimSelect:
            return "SimSelect";
        case ST::Failed:
            return "Failed";
        case ST::SanityCheck:
            return "SanityCheck";
        case ST::SimInit:
            return "SimInit";
        case ST::ModemFatalFailure:
            return "ModemFatalFailure";
        case ST::CellularConfProcedure:
            return "CellularStartConfProcedure";
        case ST::Ready:
            return "Ready";
        case ST::PowerDownStarted:
            return "PowerDownStarted";
        case ST::PowerDownWaiting:
            return "PowerDownWaiting";
        case ST::PowerDown:
            return "PowerDown";
        }
        return "";
    }

    State::State(sys::Service *owner) : owner(owner)
    {}

    const char *State::c_str() const
    {
        return State::c_str(state);
    }

    void State::set(ST state)
    {
        LOG_DEBUG("GSM state: (%s) -> (%s)", c_str(this->state), c_str(state));
        this->state = state;
        auto msg    = std::make_shared<StateChange>(static_cast<cellular::State::ST>(state));
        owner->bus.sendMulticast(msg, sys::BusChannel::ServiceCellularNotifications);
    }

    State::ST State::get() const
    {
        return this->state;
    }
} // namespace cellular::service

M module-services/service-cellular/tests/unittest_simcard.cpp => module-services/service-cellular/tests/unittest_simcard.cpp +100 -5
@@ 4,15 4,110 @@
#define CATCH_CONFIG_MAIN

#include <catch2/catch.hpp>
#include <service-cellular/SimCard.hpp>
#include <service-cellular/src/SimCard.hpp>

#include <at/Cmd.hpp>
#include <at/Result.hpp>
#include <at/ATFactory.hpp>

#include <module-cellular/test/mock/AtCommon_channel.hpp>

namespace at
{
    class QPINC_Channel : public ChannelMock
    {
      public:
        Result::Code code;
        at::EquipmentErrorCode err;
        const std::string pinAttempts;
        const std::string pukAttempts;

        QPINC_Channel(Result::Code code          = Result::Code::OK,
                      at::EquipmentErrorCode err = at::EquipmentErrorCode::NoInformation,
                      int pinAttempts            = 2,
                      int pukAttempts            = 1)
            : code(code), err(err), pinAttempts(std::to_string(pinAttempts)), pukAttempts(std::to_string(pukAttempts))
        {}

        auto cmd(const at::Cmd &at) -> Result override
        {
            const auto &cmd = at.getCmd();
            if (cmd.rfind("AT+QPINC=", 0) == 0)
                return Result(Result::Code::OK, {"+QPINC:0," + pinAttempts + "," + pukAttempts, "OK"});
            Result result(code, {});
            result.errorCode = err;
            return result;
        }
    };
} // namespace at

TEST_CASE("SimCard functionality test")
{
    SECTION("pinToString from vector")
    {
        std::vector<unsigned int> v{1, 2, 3, 4};
        std::vector<unsigned int> empty;
        REQUIRE(SimCard::pinToString(v) == "1234");
        REQUIRE(SimCard::pinToString(empty).empty());
        cellular::api::SimCode v{1, 2, 3, 4};
        cellular::api::SimCode empty;
        REQUIRE(cellular::internal::simCodeToString(v) == "1234");
        REQUIRE(cellular::internal::simCodeToString(empty).empty());
    }

    SECTION("Check Channel mockup sanity")
    {
        auto channel = at::QPINC_Channel();

        auto resp = channel.cmd(at::factory(at::AT::QPINC));
        REQUIRE(resp.code == at::Result::Code::OK);

        at::response::qpinc::AttemptsCounters ret;
        REQUIRE(at::response::parseQPINC(resp, ret));

        REQUIRE(resp.code == at::Result::Code::OK);
        REQUIRE(ret.PinCounter == 2);
        REQUIRE(ret.PukCounter == 1);
    }

    SECTION("Unlock with PIN - sucessful")
    {
        cellular::service::SimCard simCard;

        auto mockChannel = at::QPINC_Channel();
        simCard.setChannel(&mockChannel);

        bool event             = false;
        simCard.onUnhandledCME = [&event](unsigned int code) { event = true; };

        auto result = simCard.handlePinUnlock({});
        REQUIRE(result);
        REQUIRE(!event);
    }

    SECTION("Unlock with PIN - incorrect password")
    {
        cellular::service::SimCard simCard;

        auto mockChannel = at::QPINC_Channel(at::Result::Code::ERROR, at::EquipmentErrorCode::IncorrectPassword);
        simCard.setChannel(&mockChannel);

        bool event             = false;
        simCard.onUnhandledCME = [&event](unsigned int code) { event = true; };

        auto result = simCard.handlePinUnlock({});
        REQUIRE(!result);
        REQUIRE(!event);
    }

    SECTION("Unlock with PIN - unhandled CME")
    {
        cellular::service::SimCard simCard;

        auto mockChannel = at::QPINC_Channel(at::Result::Code::ERROR);
        simCard.setChannel(&mockChannel);

        bool event             = false;
        simCard.onUnhandledCME = [&event](unsigned int code) { event = true; };

        auto result = simCard.handlePinUnlock({});
        REQUIRE(!result);
        REQUIRE(event);
    }
}

M module-sys/Service/BusProxy.hpp => module-sys/Service/BusProxy.hpp +6 -0
@@ 30,6 30,12 @@ namespace sys
        void sendMulticast(std::shared_ptr<Message> message, BusChannel channel);
        void sendBroadcast(std::shared_ptr<Message> message);

        template <typename Msg, typename... Params> bool sendUnicast(Params &&... params)
        {
            auto msg = std::make_shared<Msg>(std::forward<Params>(params)...);
            return sendUnicast(msg, Msg::target);
        }

        std::vector<BusChannel> channels;

      private: