M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +1 -0
@@ 39,6 39,7 @@ set(SOURCES
${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
+ ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/QCFGUsbnet.cpp
)
add_library(${PROJECT_NAME} STATIC ${SOURCES})
M module-cellular/at/Commands.hpp => module-cellular/at/Commands.hpp +1 -0
@@ 134,6 134,7 @@ namespace at
SMS_URC_ON,
ACT_URC_OFF,
ACT_URC_ON,
+ SET_RNDIS,
};
enum class commadsSet
A module-cellular/at/cmd/QCFGUsbnet.hpp => module-cellular/at/cmd/QCFGUsbnet.hpp +47 -0
@@ 0,0 1,47 @@
+// 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 qcfg::usbnet
+ {
+ constexpr auto tokensCount = 1;
+ enum class Net
+ {
+ RMNET = 0,
+ ECM,
+ MBIM,
+ RNDIS
+ };
+ } // namespace qcfg::usbnet
+
+ namespace result
+ {
+ struct QCFGUsbnet : public Result
+ {
+ qcfg::usbnet::Net net;
+ explicit QCFGUsbnet(const Result &that);
+ };
+ } // namespace result
+
+ namespace cmd
+ {
+ class QCFGUsbnet : public Cmd
+ {
+ public:
+ QCFGUsbnet() noexcept;
+ explicit QCFGUsbnet(at::cmd::Modifier mod) noexcept;
+
+ [[nodiscard]] auto parseQCFGUsbnet(const Result &base_result) -> result::QCFGUsbnet;
+ void set(qcfg::usbnet::Net net);
+ };
+ } // namespace cmd
+
+} // namespace at
A module-cellular/at/cmd/src/QCFGUsbnet.cpp => module-cellular/at/cmd/src/QCFGUsbnet.cpp +74 -0
@@ 0,0 1,74 @@
+// 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/QCFGUsbnet.hpp>
+
+#include <log/log.hpp>
+#include <memory>
+#include <string>
+
+namespace at
+{
+ namespace cmd
+ {
+ QCFGUsbnet::QCFGUsbnet(at::cmd::Modifier mod) noexcept : Cmd("AT+QCFG=\"usbnet\"", mod, at::default_timeout)
+ {}
+
+ QCFGUsbnet::QCFGUsbnet() noexcept : QCFGUsbnet(at::cmd::Modifier::None)
+ {}
+
+ auto QCFGUsbnet::parseQCFGUsbnet(const Result &base_result) -> result::QCFGUsbnet
+ {
+
+ auto constexpr responseHeader = "+QCFG: \"usbnet\",";
+
+ result::QCFGUsbnet parsed{base_result};
+
+ if (parsed) {
+ if (parsed.response.empty()) {
+ LOG_ERROR("Can't parse - empty response");
+ parsed.code = result::QCFGUsbnet::Code::PARSING_ERROR;
+ return parsed;
+ }
+
+ std::string str = parsed.response[0];
+ if (str.find(responseHeader) == std::string::npos) {
+ LOG_ERROR("Can't parse - bad header");
+ parsed.code = result::QCFGUsbnet::Code::PARSING_ERROR;
+ return parsed;
+ }
+
+ utils::findAndReplaceAll(str, responseHeader, "");
+ utils::trim(str);
+ std::vector<std::string> tokens = utils::split(str, ',');
+ if (tokens.size() != qcfg::usbnet::tokensCount) {
+ LOG_ERROR("Can't parse - invalid tokens count");
+ parsed.code = result::QCFGUsbnet::Code::PARSING_ERROR;
+ return parsed;
+ }
+
+ auto net = 0;
+ if (utils::toNumeric(tokens[0], net) && magic_enum::enum_contains<qcfg::usbnet::Net>(net)) {
+ parsed.net = static_cast<qcfg::usbnet::Net>(net);
+ }
+ else {
+ LOG_ERROR("Can't parse - bad value");
+ parsed.code = result::QCFGUsbnet::Code::PARSING_ERROR;
+ return parsed;
+ }
+ }
+ return parsed;
+ }
+
+ void QCFGUsbnet::set(qcfg::usbnet::Net net)
+ {
+ body += utils::to_string(static_cast<int>(net));
+ }
+ } // namespace cmd
+ namespace result
+ {
+ QCFGUsbnet::QCFGUsbnet(const Result &that) : Result(that)
+ {}
+
+ } // namespace result
+} // namespace at
M module-cellular/at/src/ATFactory.cpp => module-cellular/at/src/ATFactory.cpp +1 -0
@@ 119,6 119,7 @@ namespace at
{AT::ACT_URC_ON, {"AT+QINDCFG=\"act\",1"}},
{AT::SMS_URC_ON, {"AT+QINDCFG=\"smsincoming\",1"}},
{AT::SMS_URC_OFF, {"AT+QINDCFG=\"smsincoming\",0"}},
+ {AT::SET_RNDIS, {"AT+QCFG=\"usbnet\",3"}},
};
M module-cellular/test/mock/AtCommon_channel.hpp => module-cellular/test/mock/AtCommon_channel.hpp +57 -0
@@ 509,4 509,61 @@ namespace at
return result;
}
};
+
+ /// provides invalid QSIMSTAT response
+ class QCGFGUsbnet_toLittleTokens : public ChannelMock
+ {
+ public:
+ auto ResultMock() -> Result final
+ {
+ auto result = Result();
+ result.code = Result::Code::OK;
+ result.response = {"+QCFG: \"usbnet\",", "OK"};
+ return result;
+ }
+ };
+
+ /// provides proper QCFGUsbnet response
+ class QCGFGUsbnet_successChannel : public ChannelMock
+ {
+ public:
+ auto ResultMock() -> Result final
+ {
+ auto result = Result();
+ result.code = Result::Code::OK;
+ result.response = {"+QCFG: \"usbnet\",1", "OK"};
+ return result;
+ }
+ };
+
+ /// provides invalid QCFGUsbnet response
+ class QCFGUsbnet_toManyTokens : public ChannelMock
+ {
+ public:
+ const std::string net = "1";
+ const std::string bad = "1";
+
+ auto ResultMock() -> Result final
+ {
+ auto result = Result();
+ result.code = Result::Code::OK;
+ result.response = {"+QCFG: \"usbnet\"," + net + "," + bad, "OK"};
+ return result;
+ }
+ };
+
+ /// provides invalid QCFGUsbnet response
+ class QCFGUsbnet_invalidToken : public ChannelMock
+ {
+ public:
+ const std::string net = "9";
+
+ auto ResultMock() -> Result final
+ {
+ auto result = Result();
+ result.code = Result::Code::OK;
+ result.response = {"+QCFG: \"usbnet\"," + net, "OK"};
+ return result;
+ }
+ };
} // namespace at
M module-cellular/test/unittest_parse_result.cpp => module-cellular/test/unittest_parse_result.cpp +61 -0
@@ 14,6 14,7 @@
#include <at/cmd/CPBR.hpp>
#include <at/cmd/QNWINFO.hpp>
#include <at/cmd/QSIMSTAT.hpp>
+#include <at/cmd/QCFGUsbnet.hpp>
#include "mock/AtCommon_channel.hpp"
#include "PhoneNumber.hpp"
@@ 754,3 755,63 @@ TEST_CASE("QSIMSTAT parser")
REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
}
}
+TEST_CASE("QCFGUsbnet parser")
+{
+ SECTION("Empty data")
+ {
+ at::cmd::QCFGUsbnet cmd;
+ at::Result result;
+ auto response = cmd.parseQCFGUsbnet(result);
+ REQUIRE(!response);
+ }
+
+ SECTION("Failing channel")
+ {
+ at::cmd::QCFGUsbnet cmd;
+ at::FailingChannel channel;
+ auto base = channel.cmd(cmd);
+ auto response = cmd.parseQCFGUsbnet(base);
+ REQUIRE(!response);
+ REQUIRE(response.code == at::Result::Code::ERROR);
+ }
+
+ SECTION("success")
+ {
+ at::cmd::QCFGUsbnet cmd;
+ at::QCGFGUsbnet_successChannel channel;
+ auto base = channel.cmd(cmd);
+ auto response = cmd.parseQCFGUsbnet(base);
+ REQUIRE(response);
+ REQUIRE(response.net == at::qcfg::usbnet::Net::ECM);
+ }
+
+ SECTION("too few tokens")
+ {
+ at::cmd::QCFGUsbnet cmd;
+ at::QSIMSTAT_toLittleTokens channel;
+ auto base = channel.cmd(cmd);
+ auto response = cmd.parseQCFGUsbnet(base);
+ REQUIRE(!response);
+ REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
+ }
+
+ SECTION("Failed - too many tokens")
+ {
+ at::cmd::QCFGUsbnet cmd;
+ at::QCFGUsbnet_toManyTokens channel;
+ auto base = channel.cmd(cmd);
+ auto response = cmd.parseQCFGUsbnet(base);
+ REQUIRE(!response);
+ REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
+ }
+
+ SECTION("Failed - invalid token")
+ {
+ at::cmd::QCFGUsbnet cmd;
+ at::QCFGUsbnet_invalidToken channel;
+ auto base = channel.cmd(cmd);
+ auto response = cmd.parseQCFGUsbnet(base);
+ REQUIRE(!response);
+ REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
+ }
+}
M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +1 -0
@@ 11,6 11,7 @@ set(SOURCES
src/SMSPartsHandler.cpp
src/SMSSendHandler.cpp
src/ImeiGetHandler.cpp
+ src/TerhetingHandler.cpp
CellularCall.cpp
CellularServiceAPI.cpp
M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +6 -13
@@ 102,6 102,8 @@
#include <ctime>
+#include <at/cmd/QCFGUsbnet.hpp>
+
const char *ServiceCellular::serviceName = cellular::service::name;
inline constexpr auto cellularStack = 8000;
@@ 291,24 293,13 @@ void ServiceCellular::registerMessageHandlers()
phoneModeObserver->subscribe(
[this](sys::phone_modes::PhoneMode mode) { connectionManager->onPhoneModeChange(mode); });
phoneModeObserver->subscribe([&](sys::phone_modes::Tethering tethering) {
- using bsp::cellular::USB::setPassthrough;
- using bsp::cellular::USB::PassthroughState;
if (tethering == sys::phone_modes::Tethering::On) {
- if (!tetheringTurnOffURC()) {
- LOG_ERROR("Failed to disable URC on tethering enable");
- }
+ priv->tetheringHandler->enable();
}
else {
- if (!tetheringTurnOnURC()) {
- LOG_ERROR("Failed to enable URC on tethering disable");
- }
- if (!receiveAllMessages()) {
- LOG_ERROR("Failed to receive all sms after tethering disabling");
- }
+ priv->tetheringHandler->disable();
logTetheringCalls();
}
- setPassthrough(tethering == sys::phone_modes::Tethering::On ? PassthroughState::ENABLED
- : PassthroughState::DISABLED);
});
priv->connectSimCard();
@@ 893,6 884,8 @@ bool ServiceCellular::handle_audio_conf_procedure()
return false;
}
+ priv->tetheringHandler->configure();
+
priv->state->set(State::ST::APNConfProcedure);
return true;
}
M module-services/service-cellular/src/ServiceCellularPriv.cpp => module-services/service-cellular/src/ServiceCellularPriv.cpp +44 -1
@@ 15,6 15,7 @@
#include <queries/messages/sms/QuerySMSUpdate.hpp>
#include <at/ATFactory.hpp>
+#include <at/cmd/QCFGUsbnet.hpp>
#include <ucs2/UCS2.hpp>
#include <service-appmgr/Constants.hpp>
@@ 28,10 29,12 @@ namespace cellular::internal
ServiceCellularPriv::ServiceCellularPriv(ServiceCellular *owner)
: owner{owner}, simCard{std::make_unique<SimCard>()}, state{std::make_unique<State>(owner)},
networkTime{std::make_unique<NetworkTime>()}, simContacts{std::make_unique<SimContacts>()},
- imeiGetHandler{std::make_unique<service::ImeiGetHandler>()}
+ imeiGetHandler{std::make_unique<service::ImeiGetHandler>()}, tetheringHandler{
+ std::make_unique<TetheringHandler>()}
{
initSimCard();
initSMSSendHandler();
+ initTetheringHandler();
}
void ServiceCellularPriv::initSimCard()
@@ 317,5 320,45 @@ namespace cellular::internal
return std::make_shared<cellular::GetImeiResponse>();
});
}
+ void ServiceCellularPriv::initTetheringHandler()
+ {
+ tetheringHandler->onIsRndisEnabled = [this]() -> std::optional<at::qcfg::usbnet::Net> {
+ auto channel = owner->cmux->get(CellularMux::Channel::Commands);
+ if (channel == nullptr) {
+ return std::nullopt;
+ }
+ auto command = at::cmd::QCFGUsbnet(at::cmd::Modifier::None);
+ auto response = channel->cmd(command);
+ auto result = command.parseQCFGUsbnet(response);
+ if (result) {
+ return result.net;
+ }
+ return std::nullopt;
+ };
+
+ tetheringHandler->onEnableRndis = [this]() -> bool {
+ auto channel = owner->cmux->get(CellularMux::Channel::Commands);
+ if (channel == nullptr) {
+ return false;
+ }
+ auto response = channel->cmd(at::AT::SET_RNDIS);
+ if (response) {
+ return true;
+ }
+ return false;
+ };
+
+ tetheringHandler->onEnableUrc = [this]() -> bool { return owner->tetheringTurnOnURC(); };
+
+ tetheringHandler->onDisableUrc = [this]() -> bool { return owner->tetheringTurnOffURC(); };
+
+ tetheringHandler->onSetPassthrough = [](bool enable) {
+ using bsp::cellular::USB::setPassthrough;
+ using bsp::cellular::USB::PassthroughState;
+ setPassthrough(enable ? PassthroughState::ENABLED : PassthroughState::DISABLED);
+ };
+
+ tetheringHandler->onReadMessages = [this]() -> bool { return owner->receiveAllMessages(); };
+ }
} // namespace cellular::internal
M module-services/service-cellular/src/ServiceCellularPriv.hpp => module-services/service-cellular/src/ServiceCellularPriv.hpp +4 -2
@@ 11,6 11,7 @@
#include "NetworkTime.hpp"
#include "SimContacts.hpp"
#include "ImeiGetHandler.hpp"
+#include "TetheringHandler.hpp"
namespace cellular::internal
{
@@ 18,7 19,7 @@ namespace cellular::internal
using service::SimCard;
using service::SimContacts;
using service::State;
-
+ using service::TetheringHandler;
class ServiceCellularPriv
{
ServiceCellular *owner;
@@ 29,6 30,7 @@ namespace cellular::internal
std::unique_ptr<NetworkTime> networkTime;
std::unique_ptr<SimContacts> simContacts;
std::unique_ptr<service::ImeiGetHandler> imeiGetHandler;
+ std::unique_ptr<TetheringHandler> tetheringHandler;
State::PowerState nextPowerState = State::PowerState::Off;
std::uint8_t multiPartSMSUID = 0;
@@ 47,7 49,7 @@ namespace cellular::internal
private:
void initSimCard();
void initSMSSendHandler();
-
+ void initTetheringHandler();
/** Send SMS action used by the SMSSendHandler
* \param record SMS record to send
*/
A module-services/service-cellular/src/TerhetingHandler.cpp => module-services/service-cellular/src/TerhetingHandler.cpp +48 -0
@@ 0,0 1,48 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "TetheringHandler.hpp"
+
+#include <module-cellular/at/cmd/QCFGUsbnet.hpp>
+
+namespace cellular::service
+{
+
+ auto TetheringHandler::isRndisEnabled() -> bool
+ {
+ auto result = onIsRndisEnabled();
+ if (result.has_value()) {
+ return result == at::qcfg::usbnet::Net::RNDIS;
+ }
+ return false;
+ }
+
+ auto TetheringHandler::configure() -> bool
+ {
+ if (isRndisEnabled()) {
+ return true;
+ }
+ onEnableRndis();
+ return false;
+ }
+
+ auto TetheringHandler::disable() -> bool
+ {
+ onSetPassthrough(false);
+ if (onEnableUrc() && onReadMessages()) {
+ return true;
+ }
+ LOG_FATAL("Tethering disabling failed");
+ return false;
+ }
+
+ auto TetheringHandler::enable() -> bool
+ {
+ onSetPassthrough(true);
+ if (onDisableUrc()) {
+ return true;
+ }
+ LOG_FATAL("Tethering enabling failed");
+ return false;
+ }
+} // namespace cellular::service
A module-services/service-cellular/src/TetheringHandler.hpp => module-services/service-cellular/src/TetheringHandler.hpp +56 -0
@@ 0,0 1,56 @@
+// 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 <memory>
+#include <functional>
+
+namespace at
+{
+ class Cmd;
+ class BaseChannel;
+} // namespace at
+
+namespace at::qcfg::usbnet
+{
+ enum class Net;
+}
+namespace cellular::service
+{
+
+ class TetheringHandler
+ {
+ public:
+ /**
+ * Reads current Rndis configuration
+ * @return true if Rndis is enabled, false if Rndis is disabled
+ */
+ auto isRndisEnabled() -> bool;
+ /**
+ * It's called to configure Rndis mode
+ * @return
+ */
+ auto configure() -> bool;
+ /**
+ * It's enabling tethering mode. Disables all URC and sets modem on passthrough via USB
+ * @return true on succes, false on fail
+ */
+ auto enable() -> bool;
+ /**
+ * It's disabling tethering mode. Enables all URC, reads all unreaded messages and disables passtrough via USB
+ * @return
+ */
+ auto disable() -> bool;
+
+ /**
+ * User callbacks
+ */
+ std::function<std::optional<at::qcfg::usbnet::Net>()> onIsRndisEnabled;
+ std::function<bool()> onEnableRndis;
+ std::function<bool()> onDisableUrc;
+ std::function<bool()> onEnableUrc;
+ std::function<bool()> onReadMessages;
+ std::function<void(bool)> onSetPassthrough;
+ };
+} // namespace cellular::service
M module-services/service-cellular/tests/CMakeLists.txt => module-services/service-cellular/tests/CMakeLists.txt +9 -0
@@ 66,3 66,12 @@ add_gtest_executable(
module-sys
module-cellular
)
+
+add_catch2_executable(
+ NAME
+ cellular-tethering-handler
+ SRCS
+ unittest_tetheringHandler.cpp
+ LIBS
+ module-cellular
+)
A module-services/service-cellular/tests/unittest_tetheringHandler.cpp => module-services/service-cellular/tests/unittest_tetheringHandler.cpp +107 -0
@@ 0,0 1,107 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#define CATCH_CONFIG_MAIN
+
+#include <catch2/catch.hpp>
+
+#include <module-services/service-cellular/src/TetheringHandler.hpp>
+#include <module-cellular/at/cmd/QCFGUsbnet.hpp>
+
+TEST_CASE("SMSSendHandler functionality")
+{
+ SECTION("RNDIS enabled")
+ {
+ auto handler = cellular::service::TetheringHandler();
+ handler.onIsRndisEnabled = []() -> at::qcfg::usbnet::Net { return at::qcfg::usbnet::Net::RNDIS; };
+
+ auto result = handler.isRndisEnabled();
+ REQUIRE(result == true);
+ }
+
+ SECTION("RNDIS disabled")
+ {
+
+ auto handler = cellular::service::TetheringHandler();
+ handler.onIsRndisEnabled = []() -> at::qcfg::usbnet::Net { return at::qcfg::usbnet::Net::ECM; };
+ auto result = handler.isRndisEnabled();
+
+ REQUIRE(result == false);
+ }
+
+ SECTION("configure RNDIS enabled")
+ {
+ auto handler = cellular::service::TetheringHandler();
+ auto onIsRndisEnabledCalled = 0;
+ handler.onIsRndisEnabled = [&onIsRndisEnabledCalled]() -> at::qcfg::usbnet::Net {
+ onIsRndisEnabledCalled++;
+ return at::qcfg::usbnet::Net::RNDIS;
+ };
+ auto result = handler.configure();
+ REQUIRE(result == true);
+ REQUIRE(onIsRndisEnabledCalled == 1);
+ }
+
+ SECTION("configure RNDIS disabled")
+ {
+ auto handler = cellular::service::TetheringHandler();
+ auto onIsRndisEnabledCalled = 0;
+ auto onEnableRndisCalled = 0;
+ handler.onIsRndisEnabled = [&onIsRndisEnabledCalled]() -> at::qcfg::usbnet::Net {
+ onIsRndisEnabledCalled++;
+ return at::qcfg::usbnet::Net::ECM;
+ };
+
+ handler.onEnableRndis = [&onEnableRndisCalled]() -> bool {
+ onEnableRndisCalled++;
+ return true;
+ };
+
+ auto result = handler.configure();
+ REQUIRE(result == false);
+ REQUIRE(onIsRndisEnabledCalled == 1);
+ REQUIRE(onEnableRndisCalled == 1);
+ }
+
+ SECTION("enable tethering")
+ {
+ auto handler = cellular::service::TetheringHandler();
+ auto onDisableUrcCalled = 0;
+ auto onSetPassthroughCalled = 0;
+ handler.onDisableUrc = [&onDisableUrcCalled]() -> bool {
+ onDisableUrcCalled++;
+ return true;
+ };
+
+ handler.onSetPassthrough = [&onSetPassthroughCalled](bool) { onSetPassthroughCalled++; };
+
+ auto result = handler.enable();
+ REQUIRE(result == true);
+ REQUIRE(onDisableUrcCalled == 1);
+ REQUIRE(onSetPassthroughCalled == 1);
+ }
+
+ SECTION("disable tethering")
+ {
+ auto handler = cellular::service::TetheringHandler();
+ auto onEnableUrcCalled = 0;
+ auto onReadAllMessagesCalled = 0;
+ auto onSetPassthroughCalled = 0;
+ handler.onEnableUrc = [&onEnableUrcCalled]() -> bool {
+ onEnableUrcCalled++;
+ return true;
+ };
+
+ handler.onReadMessages = [&onReadAllMessagesCalled]() -> bool {
+ onReadAllMessagesCalled++;
+ return true;
+ };
+
+ handler.onSetPassthrough = [&onSetPassthroughCalled](bool) { onSetPassthroughCalled++; };
+
+ auto result = handler.disable();
+ REQUIRE(result == true);
+ REQUIRE(onEnableUrcCalled == 1);
+ REQUIRE(onSetPassthroughCalled == 1);
+ }
+}