From 39a09e862722d241acd1897ed913b710b198002b Mon Sep 17 00:00:00 2001 From: "Wiktor S. Ovalle Correa" Date: Mon, 17 May 2021 14:50:23 +0200 Subject: [PATCH] [EGD-6746] New SimCard implementation New approach to the Cellular SimCard submodule --- .../service-cellular/CMakeLists.txt | 7 + .../include/service-cellular-api | 7 + .../include/service-cellular/api/common.hpp | 37 +++ .../api/notification/notification.hpp | 51 ++++ .../service-cellular/api/request/request.hpp | 25 ++ .../service-cellular/api/request/sim.hpp | 84 ++++++ .../include/service-cellular/state.hpp | 65 +++++ .../src/ServiceCellularPriv.cpp | 87 ++++++ .../src/ServiceCellularPriv.hpp | 33 +++ .../service-cellular/src/SimCard.cpp | 273 ++++++++++++++++++ .../service-cellular/src/SimCard.hpp | 160 ++++++++++ .../service-cellular/src/messages.hpp | 17 ++ .../service-cellular/src/state.cpp | 79 +++++ .../tests/unittest_simcard.cpp | 105 ++++++- module-sys/Service/BusProxy.hpp | 6 + 15 files changed, 1031 insertions(+), 5 deletions(-) create mode 100644 module-services/service-cellular/include/service-cellular-api create mode 100644 module-services/service-cellular/include/service-cellular/api/common.hpp create mode 100644 module-services/service-cellular/include/service-cellular/api/notification/notification.hpp create mode 100644 module-services/service-cellular/include/service-cellular/api/request/request.hpp create mode 100644 module-services/service-cellular/include/service-cellular/api/request/sim.hpp create mode 100644 module-services/service-cellular/include/service-cellular/state.hpp create mode 100644 module-services/service-cellular/src/ServiceCellularPriv.cpp create mode 100644 module-services/service-cellular/src/ServiceCellularPriv.hpp create mode 100644 module-services/service-cellular/src/SimCard.cpp create mode 100644 module-services/service-cellular/src/SimCard.hpp create mode 100644 module-services/service-cellular/src/messages.hpp create mode 100644 module-services/service-cellular/src/state.cpp diff --git a/module-services/service-cellular/CMakeLists.txt b/module-services/service-cellular/CMakeLists.txt index 901bedaaf0dab0d4ea869ea43865b4ae360becd6..e9ddc398b4cc27d888003b9f781ddfbc6309fb0b 100644 --- a/module-services/service-cellular/CMakeLists.txt +++ b/module-services/service-cellular/CMakeLists.txt @@ -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} diff --git a/module-services/service-cellular/include/service-cellular-api b/module-services/service-cellular/include/service-cellular-api new file mode 100644 index 0000000000000000000000000000000000000000..1dfde14fcf274c595ca833cb18affe4221a830f8 --- /dev/null +++ b/module-services/service-cellular/include/service-cellular-api @@ -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" diff --git a/module-services/service-cellular/include/service-cellular/api/common.hpp b/module-services/service-cellular/include/service-cellular/api/common.hpp new file mode 100644 index 0000000000000000000000000000000000000000..31c8dccd4c0745b5b19ae8a9ecb46fe3377c05ea --- /dev/null +++ b/module-services/service-cellular/include/service-cellular/api/common.hpp @@ -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 + +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; + } // namespace api +} // namespace cellular diff --git a/module-services/service-cellular/include/service-cellular/api/notification/notification.hpp b/module-services/service-cellular/include/service-cellular/api/notification/notification.hpp new file mode 100644 index 0000000000000000000000000000000000000000..cd1e0558e41d9df64b8c158f874027728876388b --- /dev/null +++ b/module-services/service-cellular/include/service-cellular/api/notification/notification.hpp @@ -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 + +#include + +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 diff --git a/module-services/service-cellular/include/service-cellular/api/request/request.hpp b/module-services/service-cellular/include/service-cellular/api/request/request.hpp new file mode 100644 index 0000000000000000000000000000000000000000..dc0c434b40f7d77e94ff8c181bb7ba2e65763af7 --- /dev/null +++ b/module-services/service-cellular/include/service-cellular/api/request/request.hpp @@ -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 + +#include + +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 diff --git a/module-services/service-cellular/include/service-cellular/api/request/sim.hpp b/module-services/service-cellular/include/service-cellular/api/request/sim.hpp new file mode 100644 index 0000000000000000000000000000000000000000..97f7445f7d9530f4f80802bf39978f5287f3f66e --- /dev/null +++ b/module-services/service-cellular/include/service-cellular/api/request/sim.hpp @@ -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 diff --git a/module-services/service-cellular/include/service-cellular/state.hpp b/module-services/service-cellular/include/service-cellular/state.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4a57c5629466e27a5b93aae8f875e89bf7163cf3 --- /dev/null +++ b/module-services/service-cellular/include/service-cellular/state.hpp @@ -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 diff --git a/module-services/service-cellular/src/ServiceCellularPriv.cpp b/module-services/service-cellular/src/ServiceCellularPriv.cpp new file mode 100644 index 0000000000000000000000000000000000000000..059e353c4f2f965c5057f856a5a6dde414d1fa75 --- /dev/null +++ b/module-services/service-cellular/src/ServiceCellularPriv.cpp @@ -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 + +#include +#include + +namespace cellular::internal +{ + ServiceCellularPriv::ServiceCellularPriv(ServiceCellular *owner) + : owner{owner}, simCard{std::make_unique()}, state{std::make_unique(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(ready), + sys::BusChannel::ServiceCellularNotifications); + }; + simCard->onNeedPin = [this](unsigned int attempts) { + owner->bus.sendMulticast(std::make_shared(attempts), + sys::BusChannel::ServiceCellularNotifications); + }; + simCard->onNeedPuk = [this](unsigned int attempts) { + owner->bus.sendMulticast(std::make_shared(attempts), + sys::BusChannel::ServiceCellularNotifications); + }; + simCard->onSimBlocked = [this]() { + owner->bus.sendMulticast(std::make_shared(), + sys::BusChannel::ServiceCellularNotifications); + }; + simCard->onSimEvent = [this]() { + owner->bus.sendUnicast(std::make_shared(), ::service::name::evt_manager); + }; + simCard->onUnhandledCME = [this](unsigned int code) { + owner->bus.sendMulticast(std::make_shared(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); + return std::make_shared(simCard->handleSetActiveSim(msg->sim)); + }); + owner->connect(typeid(request::sim::GetLockState), [&](sys::Message *) -> sys::MessagePointer { + return std::make_shared(simCard->handleIsPinLocked()); + }); + owner->connect(typeid(request::sim::ChangePin), [&](sys::Message *request) -> sys::MessagePointer { + auto msg = static_cast(request); + return std::make_shared(simCard->handleChangePin(msg->oldPin, msg->pin)); + }); + owner->connect(typeid(request::sim::UnblockWithPuk), [&](sys::Message *request) -> sys::MessagePointer { + auto msg = static_cast(request); + return std::make_shared( + simCard->handleUnblockWithPuk(msg->puk, msg->pin)); + }); + owner->connect(typeid(request::sim::SetPinLock), [&](sys::Message *request) -> sys::MessagePointer { + auto msg = static_cast(request); + return std::make_shared(simCard->handleSetPinLock(msg->pin, msg->lock), + msg->lock); + }); + owner->connect(typeid(request::sim::PinUnlock), [&](sys::Message *request) -> sys::MessagePointer { + auto msg = static_cast(request); + return std::make_shared(simCard->handlePinUnlock(msg->pin)); + }); + owner->connect(typeid(internal::msg::SimStateChanged), [&](sys::Message *request) -> sys::MessagePointer { + auto msg = static_cast(request); + simCard->handleSimStateChanged(msg->state); + return sys::MessageNone{}; + }); + } + +} // namespace cellular::internal diff --git a/module-services/service-cellular/src/ServiceCellularPriv.hpp b/module-services/service-cellular/src/ServiceCellularPriv.hpp new file mode 100644 index 0000000000000000000000000000000000000000..db0e79658b13417162cef49f70b7f254915fe830 --- /dev/null +++ b/module-services/service-cellular/src/ServiceCellularPriv.hpp @@ -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 +#include + +#include "SimCard.hpp" + +namespace cellular::internal +{ + using service::SimCard; + using service::State; + + class ServiceCellularPriv + { + ServiceCellular *owner; + + std::unique_ptr simCard; + std::unique_ptr state; + + State::PowerState nextPowerState = State::PowerState::Off; + + public: + ServiceCellularPriv(ServiceCellular *owner); + + void connectSimCard(); + + private: + void initSimCard(); + }; +} // namespace cellular::internal diff --git a/module-services/service-cellular/src/SimCard.cpp b/module-services/service-cellular/src/SimCard.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8e7a7b922385fc406ed752997c20ff590aaef6da --- /dev/null +++ b/module-services/service-cellular/src/SimCard.cpp @@ -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 +#include +#include +#include +#include +#include + +namespace cellular +{ + namespace internal + { + constexpr const char *pinString[] = {"SC", "P2"}; + + service::sim::Result convertErrorFromATResult(const at::Result atres) + { + if (std::holds_alternative(atres.errorCode)) { + + auto err = static_cast(std::get(atres.errorCode)); + if ((err > static_cast(service::sim::Result::AT_ERROR_Begin)) && + (err < static_cast(service::sim::Result::AT_ERROR_End))) { + return static_cast(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(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 SimCard::getAttemptsCounters(sim::Pin pin) const + { + if (!ready()) { + return std::nullopt; + } + + auto resp = + channel->cmd(at::factory(at::AT::QPINC) + "\"" + internal::pinString[static_cast(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(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 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(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 diff --git a/module-services/service-cellular/src/SimCard.hpp b/module-services/service-cellular/src/SimCard.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d1af37cb9169c7eb56f66036c43411f3021e6c39 --- /dev/null +++ b/module-services/service-cellular/src/SimCard.hpp @@ -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 +#include +#include + +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 onSimReady; + std::function onNeedPin; + std::function onNeedPuk; + std::function onSimBlocked; + std::function onSimEvent; + std::function 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 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 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 sim = std::nullopt; + }; + +} // namespace cellular::service diff --git a/module-services/service-cellular/src/messages.hpp b/module-services/service-cellular/src/messages.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1cb4bf20e021e169359bbc802aae62710fbd5ef3 --- /dev/null +++ b/module-services/service-cellular/src/messages.hpp @@ -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 +#include + +namespace cellular::internal::msg +{ + struct SimStateChanged : public cellular::msg::Request + { + SimStateChanged(at::SimState state) : state(state) + {} + at::SimState state; + }; +} // namespace cellular::internal::msg diff --git a/module-services/service-cellular/src/state.cpp b/module-services/service-cellular/src/state.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0044474ea3334c9f43ecc9d30500aaa82aa4518e --- /dev/null +++ b/module-services/service-cellular/src/state.cpp @@ -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 +#include +#include + +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(static_cast(state)); + owner->bus.sendMulticast(msg, sys::BusChannel::ServiceCellularNotifications); + } + + State::ST State::get() const + { + return this->state; + } +} // namespace cellular::service diff --git a/module-services/service-cellular/tests/unittest_simcard.cpp b/module-services/service-cellular/tests/unittest_simcard.cpp index 6fb9c60896b6bea5dc054e2291d17611a10df79e..2ca8e60535d8718a8b16dbd28b74ecf6aa61803c 100644 --- a/module-services/service-cellular/tests/unittest_simcard.cpp +++ b/module-services/service-cellular/tests/unittest_simcard.cpp @@ -4,15 +4,110 @@ #define CATCH_CONFIG_MAIN #include -#include +#include + +#include +#include +#include + +#include + +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 v{1, 2, 3, 4}; - std::vector 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); } } diff --git a/module-sys/Service/BusProxy.hpp b/module-sys/Service/BusProxy.hpp index eef1246be15ac413e75b4a01f19cabaa7401b746..37c1fbab64482de48f766d48a6e7c9f45ac4ce7d 100644 --- a/module-sys/Service/BusProxy.hpp +++ b/module-sys/Service/BusProxy.hpp @@ -30,6 +30,12 @@ namespace sys void sendMulticast(std::shared_ptr message, BusChannel channel); void sendBroadcast(std::shared_ptr message); + template bool sendUnicast(Params &&... params) + { + auto msg = std::make_shared(std::forward(params)...); + return sendUnicast(msg, Msg::target); + } + std::vector channels; private: