~aleteoryx/muditaos

bf8faf804ef7000d5a2f7c85ec3b5c83a9756a86 — Marcin Zieliński 3 years ago e84525a
[MOS-483] Import SIM card contacts properly

According to information elaborated by Quectel, the contacts don't have
to be saved on a SIM card contiguously and the only way to make sure all
of them are imported is to request for the range [1, 250] (first to
biggest allowed index).
7 files changed, 4 insertions(+), 297 deletions(-)

M module-cellular/CMakeLists.txt
D module-cellular/at/cmd/CPBS.hpp
D module-cellular/at/cmd/src/CPBS.cpp
M module-cellular/test/mock/AtCommon_channel.hpp
M module-cellular/test/unittest_parse_result.cpp
M module-services/service-cellular/src/SimContacts.cpp
M pure_changelog.md
M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +0 -1
@@ 35,7 35,6 @@ set(SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/QECCNUM.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/CLCC.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/CFUN.cpp
        ${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

D module-cellular/at/cmd/CPBS.hpp => module-cellular/at/cmd/CPBS.hpp +0 -47
@@ 1,47 0,0 @@
// 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>

namespace at
{
    namespace cpbs
    {
        constexpr auto simStorage  = "\"SM\"";
        constexpr auto tokensCount = 3;

        enum class tokens
        {
            Storage = 0,
            Used    = 1,
            Total   = 2
        };
    } // namespace cpbs
    namespace result
    {
        struct CPBS : public Result
        {
            std::string storage;
            unsigned int used;
            unsigned int total;

            explicit CPBS(const Result &that);
        };
    } // namespace result

    namespace cmd
    {
        class CPBS : public Cmd
        {
          public:
            CPBS() noexcept;
            explicit CPBS(at::cmd::Modifier mod) noexcept;
            [[nodiscard]] auto parseCPBS(const Result &base_result) -> result::CPBS;
            void set(const std::string &storage = cpbs::simStorage);
        };
    } // namespace cmd
} // namespace at

D module-cellular/at/cmd/src/CPBS.cpp => module-cellular/at/cmd/src/CPBS.cpp +0 -78
@@ 1,78 0,0 @@
// 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/CPBS.hpp>

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

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

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

        auto CPBS::parseCPBS(const Result &base_result) -> result::CPBS
        {

            auto constexpr responseHeader = "+CPBS: ";

            result::CPBS parsed{base_result};

            if (parsed.Result::operator bool()) {
                if (parsed.response.empty()) {
                    LOG_ERROR("Can't parse - empty response");
                    parsed.code = result::CPBS::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::CPBS::Code::PARSING_ERROR;
                        return parsed;
                    }
                    utils::findAndReplaceAll(str, responseHeader, "");
                    utils::trim(str);

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

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

                    int used  = 0;
                    int total = 0;
                    if (utils::toNumeric(tokens[static_cast<int>(cpbs::tokens::Used)], used) &&
                        utils::toNumeric(tokens[static_cast<int>(cpbs::tokens::Total)], total)) {
                        parsed.used    = used;
                        parsed.total   = total;
                        parsed.storage = tokens[static_cast<int>(cpbs::tokens::Storage)];
                    }
                    else {
                        LOG_ERROR("Can't parse - bad value");
                        parsed.code = result::CPBS::Code::PARSING_ERROR;
                    }
                }
            }
            return parsed;
        }

        void CPBS::set(const std::string &storage)
        {
            body += storage;
        }
    } // namespace cmd
    namespace result
    {
        CPBS::CPBS(const Result &that) : Result(that)
        {}

    } // namespace result
} // namespace at

M module-cellular/test/mock/AtCommon_channel.hpp => module-cellular/test/mock/AtCommon_channel.hpp +0 -50
@@ 301,56 301,6 @@ namespace at
        }
    };

    /// provides proper CPBS response
    class CPBS_successChannel : public ChannelMock
    {
      public:
        const std::string storage = "\"SM\"";
        const std::string used    = "2";
        const std::string total   = "500";
        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+CPBS: " + storage + "," + used + "," + total, "OK"};
            return result;
        }
    };

    /// provides invalid CPBS response
    class CPBS_toManyTokens : public ChannelMock
    {
      public:
        const std::string storage    = "\"SM\"";
        const std::string used       = "2";
        const std::string total      = "500";
        const std::string additional = "500";

        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+CPBS: " + storage + "," + used + "," + total + "," + additional, "OK"};
            return result;
        }
    };

    /// provides invalid CPBS response
    class CPBS_toLittleTokens : public ChannelMock
    {
      public:
        const std::string storage = "\"SM\"";
        const std::string used    = "2";

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

    /// provides proper CPBR response
    class CPBR_successChannel : public ChannelMock
    {

M module-cellular/test/unittest_parse_result.cpp => module-cellular/test/unittest_parse_result.cpp +0 -85
@@ 8,7 8,6 @@
#include <at/cmd/CSCA.hpp>
#include <at/cmd/QECCNUM.hpp>
#include <at/cmd/CFUN.hpp>
#include <at/cmd/CPBS.hpp>
#include <at/cmd/CPBR.hpp>
#include <at/cmd/QNWINFO.hpp>
#include <at/cmd/QSIMSTAT.hpp>


@@ 425,90 424,6 @@ TEST_CASE("CFUN set data")
    }
}

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

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

    SECTION("Success - valid token")
    {
        at::cmd::CPBS cmd;
        at::CPBS_successChannel channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseCPBS(base);
        REQUIRE(response.storage == "\"SM\"");
        REQUIRE(response.used == 2);
        REQUIRE(response.total == 500);
    }

    SECTION("Failed - to little tokens")
    {
        at::cmd::CPBS cmd;
        at::CPBS_toLittleTokens channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseCPBS(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
    }

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

TEST_CASE("CPBS set data")
{
    at::cmd::CPBS cmd;
    SECTION("None modifier set")
    {
        constexpr auto expectedResult = "AT+CPBS";
        REQUIRE(cmd.getCmd() == expectedResult);
    }

    SECTION("Get modifier set")
    {
        constexpr auto expectedResult = "AT+CPBS?";
        cmd.setModifier(at::cmd::Modifier::Get);
        REQUIRE(cmd.getCmd() == expectedResult);
    }

    SECTION("Set modifier set")
    {
        constexpr auto expectedResult = "AT+CPBS=";
        cmd.setModifier(at::cmd::Modifier::Set);
        REQUIRE(cmd.getCmd() == expectedResult);
    }

    SECTION("Set commnad")
    {
        constexpr auto expectedResult = "AT+CPBS=\"SM\"";
        cmd                           = at::cmd::CPBS(at::cmd::Modifier::Set);
        cmd.set();
        REQUIRE(cmd.getCmd() == expectedResult);
    }
}

TEST_CASE("CPBR parser")
{
    SECTION("Empty data")

M module-services/service-cellular/src/SimContacts.cpp => module-services/service-cellular/src/SimContacts.cpp +3 -36
@@ 4,7 4,6 @@
#include "SimContacts.hpp"

#include <modem/BaseChannel.hpp>
#include <at/cmd/CPBS.hpp>
#include <at/cmd/CPBR.hpp>

namespace cellular::service


@@ 16,18 15,14 @@ namespace cellular::service

    auto SimContacts::getContacts(std::vector<cellular::SimContact> &destination) -> bool
    {
        constexpr auto firstIndex = 1;
        if (channel == nullptr) {
        if (!channel) {
            LOG_ERROR("No channel provided. Request ignored");
            return false;
        }
        auto contactsCount = 0;
        if (!getContactCount(contactsCount)) {
            return false;
        }

        auto command = at::cmd::CPBR(at::cmd::Modifier::Set);
        command.setSimContactsReadRange(firstIndex, contactsCount);
        constexpr int firstIndex = 1, maxIndex = 250;
        command.setSimContactsReadRange(firstIndex, maxIndex);
        auto response = channel->cmd(command);
        auto result   = command.parseCPBR(response);



@@ 39,35 34,7 @@ namespace cellular::service
        for (auto el : result.contacts) {
            destination.push_back(SimContact(el.name, el.number));
        }
        return true;
    }

    auto SimContacts::getContactCount(int &count) -> bool
    {
        if (channel == nullptr) {
            LOG_ERROR("No channel provided. Request ignored");
            return false;
        }

        auto command = at::cmd::CPBS(at::cmd::Modifier::Set);
        command.set();
        auto response = channel->cmd(command);

        if (response.code != at::Result::Code::OK) {
            LOG_ERROR("Failed to set contact storage");
            return false;
        }

        command     = at::cmd::CPBS(at::cmd::Modifier::Get);
        response    = channel->cmd(command);
        auto result = command.parseCPBS(response);

        if (result.code != at::Result::Code::OK) {
            LOG_ERROR("Failed to get contacts count");
            return false;
        }

        count = result.used;
        return true;
    }
} // namespace cellular::service

M pure_changelog.md => pure_changelog.md +1 -0
@@ 55,6 55,7 @@
* Fixed the data displayed on snoozed alarms screen
* Fixed displaying an incorrect window after double ending a phone call
* Fixed templates list not looping
* Fixed inability to import contacts from Orange SIM cards

## [1.5.0 2022-12-20]