M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +1 -0
@@ 31,6 31,7 @@ set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcCmti.cpp
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcClip.cpp
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcCpin.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcQiurc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcPoweredDown.cpp
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcResponse.cpp
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcFactory.cpp
M module-cellular/at/Commands.hpp => module-cellular/at/Commands.hpp +8 -2
@@ 151,7 151,10 @@ namespace at
COLP_GET,
COLP_ENABLE,
COLP_DISABLE,
- CSSN, /// Supplementary Services - Supplementary Service Notifications
+ CSSN, /// Supplementary Services - Supplementary Service Notifications
+ QICSGP, /// Configure Parameters of a TCP/IP Context
+ QIACT, /// Activate a PDP Context
+ QIDEACT /// Deactivate a PDP Context
};
// below timeouts are defined in Quectel_EC25&EC21_AT_Commands_Manual_V1.3.pdf
@@ 248,7 251,10 @@ namespace at
{AT::COLP_GET, {"AT+COLP?", default_long_doc_timeout}},
{AT::COLP_ENABLE, {"AT+COLP=1", default_long_doc_timeout}},
{AT::COLP_DISABLE, {"AT+COLP=0", default_long_doc_timeout}},
- {AT::CSSN, {"AT+CSSN=\"", default_doc_timeout}}};
+ {AT::CSSN, {"AT+CSSN=\"", default_doc_timeout}},
+ {AT::QICSGP, {"AT+QICSGP", default_timeout}},
+ {AT::QIACT, {"AT+QIACT", 150000}},
+ {AT::QIDEACT, {"AT+QIDEACT", 40000}}};
if (fact.count(at)) {
return fact.at(at);
M module-cellular/at/UrcClip.hpp => module-cellular/at/UrcClip.hpp +1 -1
@@ 31,7 31,7 @@ namespace at::urc
};
static constexpr std::string_view head = "+CLIP";
- static bool isURC(const std::string uHead)
+ static bool isURC(const std::string &uHead)
{
return uHead.find(Clip::head) != std::string::npos;
}
M module-cellular/at/UrcCmti.hpp => module-cellular/at/UrcCmti.hpp +1 -1
@@ 17,7 17,7 @@ namespace at::urc
public:
static constexpr std::string_view head = "+CMTI";
- static auto isURC(const std::string uHead) -> bool
+ static auto isURC(const std::string &uHead) -> bool
{
return uHead.find(Cmti::head) != std::string::npos;
}
M module-cellular/at/UrcCpin.hpp => module-cellular/at/UrcCpin.hpp +1 -1
@@ 43,7 43,7 @@ namespace at::urc
static constexpr auto head = "+CPIN";
- static bool isURC(const std::string uHead)
+ static bool isURC(const std::string &uHead)
{
return uHead.find(Cpin::head) != std::string::npos;
}
M module-cellular/at/UrcCreg.hpp => module-cellular/at/UrcCreg.hpp +1 -1
@@ 24,7 24,7 @@ namespace at::urc
public:
static constexpr std::string_view head = "+CREG";
- static bool isURC(const std::string uHead)
+ static bool isURC(const std::string &uHead)
{
return uHead.find(Creg::head) != std::string::npos;
}
M module-cellular/at/UrcCtze.hpp => module-cellular/at/UrcCtze.hpp +1 -1
@@ 20,7 20,7 @@ namespace at::urc
public:
static constexpr std::string_view head = "+CTZE";
- static auto isURC(const std::string uHead) -> bool
+ static auto isURC(const std::string &uHead) -> bool
{
return uHead.find(Ctze::head) != std::string::npos;
}
M module-cellular/at/UrcCusd.hpp => module-cellular/at/UrcCusd.hpp +1 -1
@@ 32,7 32,7 @@ namespace at::urc
};
Cusd(const std::string &urcBody, const std::string &urcHead = std::string());
static constexpr std::string_view head = "+CUSD";
- static auto isURC(const std::string uHead) -> bool
+ static auto isURC(const std::string &uHead) -> bool
{
return uHead.find(Cusd::head) != std::string::npos;
}
M module-cellular/at/UrcHandler.hpp => module-cellular/at/UrcHandler.hpp +2 -0
@@ 12,6 12,7 @@ namespace at::urc
class Ctze;
class Qind;
class Cpin;
+ class Qiurc;
class PoweredDown;
class UrcResponse;
@@ 25,6 26,7 @@ namespace at::urc
virtual void Handle(Ctze &urc) = 0;
virtual void Handle(Qind &urc) = 0;
virtual void Handle(Cpin &urc) = 0;
+ virtual void Handle(Qiurc &urc) = 0;
virtual void Handle(PoweredDown &urc) = 0;
virtual void Handle(UrcResponse &urc) = 0;
};
M module-cellular/at/UrcPoweredDown.hpp => module-cellular/at/UrcPoweredDown.hpp +1 -1
@@ 12,7 12,7 @@ namespace at::urc
public:
static constexpr std::string_view head_immediate = "POWERED DOWN";
static constexpr std::string_view head_normal = "NORMAL POWER DOWN";
- static auto isURC(const std::string uHead) -> bool
+ static auto isURC(const std::string &uHead) -> bool
{
auto isImmediatePowerDown = uHead.find(PoweredDown::head_immediate) != std::string::npos;
auto isNormalPowerDown = uHead.find(PoweredDown::head_normal) != std::string::npos;
M module-cellular/at/UrcQind.hpp => module-cellular/at/UrcQind.hpp +1 -1
@@ 48,7 48,7 @@ namespace at::urc
};
static constexpr std::string_view head = "+QIND";
- static auto isURC(const std::string uHead) -> bool
+ static auto isURC(const std::string &uHead) -> bool
{
return uHead.find(Qind::head) != std::string::npos;
}
A module-cellular/at/UrcQiurc.hpp => module-cellular/at/UrcQiurc.hpp +54 -0
@@ 0,0 1,54 @@
+// 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 "Urc.hpp"
+
+namespace at::urc
+{
+
+ /// +QIURC: <action>,[p1], [p2] - multipurpose URC information (in general for TCP connection)
+ class Qiurc : public Urc
+ {
+ /**
+ * In general could be at last one param in +QIURC: "incoming full"
+ * to such with CR LF messages contain data +QIURC:
+ * "recv",<connectID>,<currentrecvlength>,<remoteIP>,<remote_port><CR><LF><data>
+ */
+ enum Tokens
+ {
+ Type = 0,
+ FirstParam = 1
+ };
+
+ public:
+ enum class QIUrcMessages
+ {
+ DeactivateContext = 1
+ };
+ static constexpr auto qiurcPdpdeact = "pdpdeact"; ///< +QIURC:"pdpdeact",<contextID>
+ static constexpr auto qiurcPdpdeactCount = 2;
+
+ static constexpr auto head = "+QIURC";
+
+ static bool isURC(const std::string &uHead)
+ {
+ return uHead.find(Qiurc::head) != std::string::npos;
+ }
+
+ using Urc::Urc;
+
+ [[nodiscard]] auto isValid() const noexcept -> bool override;
+
+ [[nodiscard]] auto getType() const noexcept -> std::optional<QIUrcMessages>;
+
+ [[nodiscard]] auto getFirstParam() const noexcept -> std::optional<std::string>;
+
+ void Handle(UrcHandler &h) final
+ {
+ h.Handle(*this);
+ }
+ };
+
+} // namespace at::urc
M module-cellular/at/UrcResponse.hpp => module-cellular/at/UrcResponse.hpp +1 -1
@@ 23,7 23,7 @@ namespace at::urc
NoAnswer
};
- static auto isURC(const std::string uHead) -> std::optional<URCResponseType>
+ static auto isURC(const std::string &uHead) -> std::optional<URCResponseType>
{
for (auto &resp : urcResponses) {
if (uHead.find(resp.second) != std::string::npos) {
M module-cellular/at/response.cpp => module-cellular/at/response.cpp +19 -4
@@ 11,7 11,6 @@ namespace at
{
namespace response
{
- constexpr auto StringDelimiter = "\"";
std::optional<std::string> getResponseLineATCommand(const at::Result &resp, std::string_view head)
{
@@ 36,6 35,21 @@ namespace at
}
return std::nullopt;
}
+ std::optional<ResponseTokens> getTokensForATResults(const at::Result &resp, std::string_view head)
+ {
+ if (resp.code != at::Result::Code::OK)
+ return std::nullopt;
+
+ std::vector<std::vector<std::string>> parts;
+ for (auto el : resp.response) {
+ if (el.compare(0, head.length(), head) == 0) {
+ auto body = el.substr(head.length());
+ parts.push_back(utils::split(body, ","));
+ }
+ }
+
+ return parts;
+ }
bool parseCOPS(const at::Result &resp, std::vector<cops::Operator> &ret)
{
@@ 104,9 118,10 @@ namespace at
const std::string_view AT_QPINC_SC = "+QPINC:";
if (auto tokens = getTokensForATCommand(resp, AT_QPINC_SC); tokens) {
constexpr int QPINC_TokensCount = 3;
- if ((*tokens).size() == QPINC_TokensCount) {
- utils::toNumeric((*tokens)[1], ret.PinCounter);
- utils::toNumeric((*tokens)[2], ret.PukCounter);
+ auto pinc_tokens = (*tokens);
+ if (pinc_tokens.size() == QPINC_TokensCount) {
+ utils::toNumeric(pinc_tokens[1], ret.PinCounter);
+ utils::toNumeric(pinc_tokens[2], ret.PukCounter);
return true;
}
}
M module-cellular/at/response.hpp => module-cellular/at/response.hpp +26 -1
@@ 15,6 15,7 @@ namespace at
{
namespace response
{
+ constexpr auto StringDelimiter = "\"";
namespace qpinc
{
/// Structure that holds parsed information from AT+QPINC command
@@ 67,9 68,33 @@ namespace at
} // namespace cops
bool parseCOPS(const at::Result &resp, std::vector<cops::Operator> &ret);
-
+ using ResponseTokens = std::vector<std::vector<std::string>>;
std::vector<std::string> tokenize(std::string &response, std::string separator = ",");
+
+ /**
+ * For AT one line (+XYZ) response like:
+ * +CPIN READY
+ * OK
+ */
std::optional<std::vector<std::string>> getTokensForATCommand(const at::Result &resp, std::string_view head);
+
+ /**
+ * For AT multiline response like (last OK)
+ * +QIACT:1,<context_state>,<context_type>[,<IP_address>]
+ * [.....
+ * +QIACT:16,<context_state>,<context_type>[,<IP_address>]]
+ * OK
+ *
+ * response from function like QPING (not mention in DOC as URC), looks like (first OK)
+ * OK
+ * +QPING: 0,"61.135.169.125",32,192,255
+ * +QPING: 0,"61.135.169.125",32,240,255
+ * ...
+ * +QPING: 0,4,4,0,192,479,287
+ *
+ * Warning: should not be used for URC !
+ */
+ std::optional<ResponseTokens> getTokensForATResults(const at::Result &resp, std::string_view head);
bool parseCSQ(std::string response, std::string &result);
bool parseCSQ(std::string cellularResponse, uint32_t &result);
bool parseCREG(std::string &response, uint32_t &result);
M module-cellular/at/src/UrcFactory.cpp => module-cellular/at/src/UrcFactory.cpp +4 -0
@@ 12,6 12,7 @@
#include <UrcPoweredDown.hpp>
#include <UrcResponse.hpp>
#include <UrcCpin.hpp>
+#include <UrcQiurc.hpp>
using namespace at::urc;
std::unique_ptr<Urc> UrcFactory::Create(const std::string &urcMessage)
@@ 51,6 52,9 @@ std::unique_ptr<Urc> UrcFactory::Create(const std::string &urcMessage)
else if (auto type = UrcResponse::isURC(head)) {
return std::make_unique<UrcResponse>(type.value());
}
+ else if (Qiurc::isURC(head)) {
+ return std::make_unique<Qiurc>(body);
+ }
return std::make_unique<Urc>(body, head);
}
A module-cellular/at/src/UrcQiurc.cpp => module-cellular/at/src/UrcQiurc.cpp +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
+
+#include "UrcQiurc.hpp"
+
+using namespace at::urc;
+
+auto Qiurc::isValid() const noexcept -> bool
+{
+ return tokens.size() == qiurcPdpdeactCount; /// only support one message type
+}
+
+auto Qiurc::getType() const noexcept -> std::optional<QIUrcMessages>
+{
+ if (!isValid()) {
+ return std::nullopt;
+ }
+
+ if (tokens[Tokens::Type] == qiurcPdpdeact) {
+ return QIUrcMessages::DeactivateContext;
+ }
+ return std::nullopt;
+}
+
+auto Qiurc::getFirstParam() const noexcept -> std::optional<std::string>
+{
+ if (getType()) {
+ if (isValid() && (*getType() == QIUrcMessages::DeactivateContext)) {
+ return tokens[Tokens::FirstParam];
+ }
+ }
+ return std::nullopt;
+}
M module-cellular/test/unittest_URC.cpp => module-cellular/test/unittest_URC.cpp +33 -0
@@ 17,6 17,7 @@
#include "UrcCmti.hpp"
#include "UrcClip.hpp"
#include "UrcCpin.hpp"
+#include "UrcQiurc.hpp"
#include "UrcPoweredDown.hpp"
#include "UrcResponse.hpp"
#include "UrcFactory.hpp"
@@ 808,3 809,35 @@ TEST_CASE("+Qind: SMS DONE")
REQUIRE(qind->isSmsDone());
}
}
+
+TEST_CASE("+Qiurc: TCP Context and connection message")
+{
+ SECTION("PDP Context deactivate - normal message")
+ {
+ /// +QIURC:"pdpdeact",<contextID>
+
+ auto urc = at::urc::UrcFactory::Create("+QIURC: \"pdpdeact\",1");
+ auto qiurc = getURC<at::urc::Qiurc>(urc);
+
+ REQUIRE(qiurc);
+ REQUIRE(qiurc->getType());
+ REQUIRE(qiurc->isValid());
+ REQUIRE(*qiurc->getType() == at::urc::Qiurc::QIUrcMessages::DeactivateContext);
+ REQUIRE(*qiurc->getFirstParam() == "1");
+ }
+
+ SECTION("PDP Context deactivate - corrupted, but OK format")
+ {
+ auto urc = at::urc::UrcFactory::Create("+QIURC:\"pdpdeactwww\",1");
+ auto qiurc = getURC<at::urc::Qiurc>(urc);
+ REQUIRE(qiurc);
+ REQUIRE(qiurc->getType() == std::nullopt);
+ }
+
+ SECTION("PDP Context deactivate - wrong param count (in case simple implementation)")
+ {
+ auto urc = at::urc::UrcFactory::Create("+QIURC:\"pdpdeactwww\",1,3");
+ auto qiurc = getURC<at::urc::Qiurc>(urc);
+ REQUIRE(qiurc->getType() == std::nullopt);
+ }
+}
M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +2 -1
@@ 1,4 1,4 @@
-project(service-cellular)
+project(service-cellular)
message( "${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}" )
set(SOURCES
@@ 9,6 9,7 @@ set(SOURCES
SignalStrength.cpp
SimCard.cpp
NetworkSettings.cpp
+ PacketData.cpp
RequestFactory.cpp
CellularRequestHandler.cpp
requests/Request.cpp
M module-services/service-cellular/CellularServiceAPI.cpp => module-services/service-cellular/CellularServiceAPI.cpp +34 -5
@@ 253,13 253,11 @@ bool CellularServiceAPI::GetAntenna(sys::Service *serv, bsp::cellular::antenna &
bool CellularServiceAPI::TransmitDtmfTones(sys::Service *serv, uint32_t digit)
{
auto msg = std::make_shared<CellularDtmfRequestMessage>(digit);
-
return sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
}
bool CellularServiceAPI::USSDRequest(sys::Service *serv, CellularUSSDMessage::RequestType type, std::string data)
{
-
auto msg = std::make_shared<CellularUSSDMessage>(type, data);
sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
return true;
@@ 270,7 268,6 @@ bool CellularServiceAPI::ChangeSimPin(sys::Service *serv,
const std::vector<unsigned int> &passcode,
const std::vector<unsigned int> &pin)
{
-
return sys::Bus::SendUnicast(
std::make_shared<CellularSimPukDataMessage>(sim, passcode, pin), ServiceCellular::serviceName, serv);
}
@@ 280,14 277,46 @@ bool CellularServiceAPI::SetSimCardLock(sys::Service *serv,
CellularSimCardLockDataMessage::SimCardLock lock,
const std::vector<unsigned int> &pin)
{
-
return sys::Bus::SendUnicast(
std::make_shared<CellularSimCardLockDataMessage>(sim, lock, pin), ServiceCellular::serviceName, serv);
}
bool CellularServiceAPI::SetSimCard(sys::Service *serv, Store::GSM::SIM sim)
{
-
return sys::Bus::SendUnicast(
std::make_shared<CellularChangeSimDataMessage>(sim), ServiceCellular::serviceName, serv);
}
+
+bool CellularServiceAPI::GetAPN(sys::Service *serv)
+{
+ return sys::Bus::SendUnicast(std::make_shared<CellularGetAPNMessage>(), ServiceCellular::serviceName, serv);
+}
+
+bool CellularServiceAPI::GetAPN(sys::Service *serv, std::uint8_t contextId)
+{
+ return sys::Bus::SendUnicast(
+ std::make_shared<CellularGetAPNMessage>(contextId), ServiceCellular::serviceName, serv);
+}
+
+bool CellularServiceAPI::GetAPN(sys::Service *serv, packet_data::APN::APNType type)
+{
+ return sys::Bus::SendUnicast(std::make_shared<CellularGetAPNMessage>(type), ServiceCellular::serviceName, serv);
+}
+
+bool CellularServiceAPI::SetAPN(sys::Service *serv, packet_data::APN::Config apnConfig)
+{
+ auto apn = std::make_shared<packet_data::APN::Config>(std::move(apnConfig));
+ return sys::Bus::SendUnicast(std::make_shared<CellularSetAPNMessage>(apn), ServiceCellular::serviceName, serv);
+}
+
+bool CellularServiceAPI::SetDataTransfer(sys::Service *serv, packet_data::DataTransfer dt)
+{
+ return sys::Bus::SendUnicast(
+ std::make_shared<CellularSetDataTransferMessage>(dt), ServiceCellular::serviceName, serv);
+}
+
+bool CellularServiceAPI::GetDataTransfer(sys::Service *serv)
+{
+ return sys::Bus::SendUnicast(
+ std::make_shared<CellularGetDataTransferMessage>(), ServiceCellular::serviceName, serv);
+}
M module-services/service-cellular/CellularUrcHandler.cpp => module-services/service-cellular/CellularUrcHandler.cpp +18 -0
@@ 155,6 155,24 @@ void CellularUrcHandler::Handle(Cpin &urc)
}
}
+void CellularUrcHandler::Handle(Qiurc &urc)
+{
+ auto urcType = urc.getType();
+ if (urc.isValid() && urcType) {
+ switch (*urcType) {
+ case Qiurc::QIUrcMessages::DeactivateContext:
+ if (auto urcFirstParam = urc.getFirstParam(); urcFirstParam) {
+ int ctxid = 0;
+ if (utils::toNumeric(*urcFirstParam, ctxid)) {
+ response = std::make_unique<CellularDeactivateContextResponse>(at::Result::Code::OK, ctxid);
+ urc.setHandled(true);
+ }
+ }
+ break;
+ }
+ }
+}
+
void CellularUrcHandler::Handle(PoweredDown &urc)
{
if (urc.isValid()) {
M module-services/service-cellular/CellularUrcHandler.hpp => module-services/service-cellular/CellularUrcHandler.hpp +2 -0
@@ 16,6 16,7 @@
#include <module-cellular/at/UrcPoweredDown.hpp>
#include <module-cellular/at/UrcQind.hpp>
#include <module-cellular/at/UrcResponse.hpp>
+#include <module-cellular/at/UrcQiurc.hpp>
using namespace at::urc;
@@ 35,6 36,7 @@ class CellularUrcHandler : public UrcHandler
void Handle(Ctze &urc) final;
void Handle(Qind &urc) final;
void Handle(Cpin &urc) final;
+ void Handle(Qiurc &urc) final;
void Handle(PoweredDown &urc) final;
void Handle(UrcResponse &urc) final;
A module-services/service-cellular/PacketData.cpp => module-services/service-cellular/PacketData.cpp +317 -0
@@ 0,0 1,317 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "PacketData.hpp"
+
+#include <optional>
+#include <algorithm>
+#include <iterator>
+
+#include <response.hpp>
+#include <Utils.hpp>
+namespace at
+{
+ namespace response
+ {
+ bool parseQICSGP(const at::Result &resp, std::shared_ptr<packet_data::APN::Config> retAPN)
+ {
+
+ /// AT+QICSGP=<contextID>[,<context_type>,<APN>[,<username>,<password>)[,<authentication>[,<cdma_pwd>]]]]
+ /// +QICSGP: <context_type>,<APN>,<username>,<password>,<authentication>
+ /// +QICSGP: 1,"","","",0
+ constexpr auto AT_QICSGP = "+QICSGP:";
+ if (auto tokens = at::response::getTokensForATCommand(resp, AT_QICSGP); tokens) {
+
+ constexpr int QICSGP_TokensCount = 5; /// Could be depend ? on firmware version, assume that always have
+ /// <context_type>,<APN>,<username>,<password>,<authentication>
+ if ((*tokens).size() == QICSGP_TokensCount) {
+
+ retAPN->contextType =
+ static_cast<packet_data::APN::ContextType>(utils::getNumericValue<unsigned int>((*tokens)[0]));
+ retAPN->apn = (*tokens)[1];
+ utils::findAndReplaceAll(retAPN->apn, at::response::StringDelimiter, "");
+ retAPN->username = (*tokens)[2];
+ utils::findAndReplaceAll(retAPN->username, at::response::StringDelimiter, "");
+ retAPN->password = (*tokens)[3];
+ utils::findAndReplaceAll(retAPN->password, at::response::StringDelimiter, "");
+ retAPN->authMethod =
+ static_cast<packet_data::APN::AuthMethod>(utils::getNumericValue<unsigned int>((*tokens)[4]));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool parseQIACT(const at::Result &resp, std::vector<std::shared_ptr<packet_data::APN::Config>> &ret)
+ {
+
+ /**
+ * In case of AT+QIACT?
+ * +QIACT:1,<context_state>,<context_type>[,<IP_address>]
+ * [.....
+ * +QIACT:16,<context_state>,<context_type>[,<IP_address>]]
+ *
+ * also could be empty list
+ */
+ constexpr int QIACT_TokensCount = 3;
+ constexpr int QIACT_WithIP_TokensCount = 4;
+
+ constexpr auto AT_QIACT = "+QIACT:";
+ if (auto mtokens = at::response::getTokensForATResults(resp, AT_QIACT); mtokens) {
+ for (const auto &tokens : *mtokens) {
+
+ if (tokens.size() < QIACT_TokensCount) {
+ continue;
+ }
+ std::shared_ptr<packet_data::APN::Config> retAPN = std::make_shared<packet_data::APN::Config>();
+ retAPN->contextId = utils::getNumericValue<std::uint8_t>(tokens[0]);
+ retAPN->state =
+ static_cast<packet_data::APN::ContextState>(utils::getNumericValue<std::uint8_t>(tokens[1]));
+ retAPN->contextType =
+ static_cast<packet_data::APN::ContextType>(utils::getNumericValue<std::uint8_t>(tokens[2]));
+
+ if (tokens.size() >= QIACT_WithIP_TokensCount) {
+ retAPN->ip = tokens[3];
+ utils::findAndReplaceAll(retAPN->ip, at::response::StringDelimiter, "");
+ }
+ ret.push_back(retAPN);
+ }
+ return true; /// empty list is also good result
+ }
+ return false;
+ }
+ } // namespace response
+ namespace query
+ {
+ std::string prepareQICSGP(std::shared_ptr<packet_data::APN::Config> apn, bool setEmpty)
+ {
+ /// AT+QICSGP=<contextID>[,<context_type>,<APN>[,<username>,<password>)[,<authentication>]]]
+
+ if (setEmpty) {
+ LOG_DEBUG("Set empty APN");
+ return at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast<int>(apn->contextId)) + "," +
+ "1,\"\",\"\",\"\",1";
+ }
+
+ return at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast<int>(apn->contextId)) + "," +
+ utils::to_string(static_cast<int>(apn->contextType)) + ",\"" + apn->apn + "\",\"" + apn->username +
+ "\",\"" + apn->password + "\"," + utils::to_string(static_cast<int>(apn->authMethod));
+ }
+
+ } // namespace query
+} // namespace at
+
+namespace packet_data
+{
+
+ PDPContext::PDPContext(ServiceCellular &cellularService) : cellularService(cellularService)
+ {
+ channel = cellularService.cmux->get(TS0710::Channel::Commands);
+ }
+
+ std::shared_ptr<APN::Config> PDPContext::getConfiguration(std::uint8_t contextId)
+ {
+ if (channel) {
+ auto resp = channel->cmd(at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast<int>(contextId)));
+ if (resp.code == at::Result::Code::OK) {
+ std::shared_ptr<APN::Config> ret = std::make_shared<APN::Config>();
+ ret->contextId = contextId;
+ if (at::response::parseQICSGP(resp, ret)) {
+ return ret;
+ }
+ }
+ }
+ return nullptr;
+ }
+
+ at::Result::Code PDPContext::setConfiguration(std::shared_ptr<APN::Config> apn, bool setEmpty)
+ {
+ if (channel) {
+ auto resp = channel->cmd(at::query::prepareQICSGP(apn, setEmpty));
+ return resp.code;
+ }
+ return at::Result::Code::ERROR;
+ }
+
+ at::Result::Code PDPContext::activate(std::uint8_t contextId)
+ {
+
+ if (channel) {
+ auto resp = channel->cmd(at::factory(at::AT::QIACT) + "=" + utils::to_string(static_cast<int>(contextId)));
+
+ /**
+ * From quectel documentation:
+ * 1. Reboot the module if there is no response in 150s.
+ * 2. If failed to deactivate the PDP context for 3 times continuously, then reboot the module.
+ */
+ return resp.code;
+ }
+ return at::Result::Code::ERROR;
+ }
+ at::Result::Code PDPContext::deactivate(std::uint8_t contextId)
+ {
+ if (channel) {
+ /// this command could generate URC, deactivate
+ auto resp =
+ channel->cmd(at::factory(at::AT::QIDEACT) + "=" + utils::to_string(static_cast<int>(contextId)));
+
+ return resp.code;
+ }
+ return at::Result::Code::ERROR;
+ }
+
+ std::optional<const std::vector<std::shared_ptr<APN::Config>>> PDPContext::getActive()
+ {
+ if (channel) {
+ auto resp = channel->cmd(at::factory(at::AT::QIACT) + "?");
+
+ if (resp.code == at::Result::Code::OK) {
+ std::vector<std::shared_ptr<APN::Config>> ret;
+ if (at::response::parseQIACT(resp, ret)) {
+ return ret;
+ }
+ }
+ }
+ return std::nullopt;
+ }
+
+ PacketData::PacketData(ServiceCellular &cellularService) : cellularService(cellularService){};
+
+ void PacketData::loadAPNSettings()
+ {
+
+ std::shared_ptr<APN::Config> apnConfig = std::make_shared<APN::Config>();
+ apnConfig->contextId = 1;
+ apnConfig->apnType = APN::APNType::Default;
+ apnConfig->apn = "internet";
+ apnConfig->authMethod = APN::AuthMethod::NONE;
+
+ contextMap[apnConfig->contextId] = apnConfig;
+
+ LOG_ERROR("loadAPNSettings");
+ }
+
+ void PacketData::saveAPNSettings()
+ {
+ /// Save in phone memory
+ }
+
+ at::Result::Code PacketData::updateAPNSettings(std::uint8_t ctxId)
+ {
+ LOG_DEBUG("updateAPNSettings %d", ctxId);
+ PDPContext pdpCtx(cellularService);
+ std::shared_ptr<APN::Config> apnConfig;
+ std::shared_ptr<APN::Config> modemApn;
+ if ((modemApn = pdpCtx.getConfiguration(ctxId)) && (modemApn != nullptr)) {
+
+ auto phoneApn = contextMap.find(ctxId);
+
+ if (phoneApn != contextMap.end()) {
+ LOG_DEBUG("Phone context exists");
+ if (dataTransfer == DataTransfer::Off) {
+ /// set null configuration, solution based on lack of quectel documentation
+ return pdpCtx.setConfiguration(phoneApn->second, true);
+ }
+ else {
+ if (!phoneApn->second->compare(modemApn)) {
+ /// rebuild configuratio
+ LOG_DEBUG("Update modem context %d", ctxId);
+ return pdpCtx.setConfiguration(phoneApn->second);
+ }
+ }
+ }
+ else {
+ LOG_ERROR("Phone context not exists");
+ if (!modemApn->isEmpty()) {
+ /** update phone configuration base on modem conf (eg. ims)
+ *
+ * Comment from quectel (2020.12):
+ * As I know, only VZW MBN would use IMS on CID 1 to register IMS service directly.
+ * So we usually ask customers to set up data connection on CID3 for VZW sim card.
+ * Regarding other MBN files, the CID 1 shouldn’t be IMS because the CID1 is used
+ * to activate default bearer for LTE network. So we usually ask customers to
+ * configure their own APN on CID1. With this rule, it’s easy for customer
+ * to configure their APN no matter which MBN file is activated
+ */
+ LOG_ERROR("Update modem context %d", ctxId);
+ return pdpCtx.setConfiguration(modemApn, true);
+ }
+ }
+ }
+ return at::Result::Code::OK;
+ }
+ void PacketData::setupAPNSettings()
+ {
+ for (std::uint8_t ctxId = MINContextId; ctxId <= MAXContextId; ctxId++) {
+ updateAPNSettings(ctxId);
+ }
+ }
+
+ std::optional<std::shared_ptr<APN::Config>> PacketData::getAPN(std::uint8_t ctxid)
+ {
+ auto apn = std::find_if(contextMap.begin(), contextMap.end(), [&ctxid](const ContextPair &pair) {
+ return pair.second->contextId == ctxid;
+ });
+
+ if (apn != contextMap.end()) {
+ return apn->second;
+ }
+
+ return std::nullopt;
+ }
+
+ const std::vector<std::shared_ptr<APN::Config>> PacketData::getAPNs() const
+ {
+ std::vector<std::shared_ptr<APN::Config>> vconf;
+ std::transform(
+ contextMap.begin(), contextMap.end(), std::back_inserter(vconf), [](auto &cm) { return cm.second; });
+ return vconf;
+ }
+
+ std::optional<std::shared_ptr<APN::Config>> PacketData::getAPNFirst(APN::APNType type)
+ {
+
+ auto apn = std::find_if(contextMap.begin(), contextMap.end(), [&type](const ContextPair &pair) -> bool {
+ return pair.second->apnType == type;
+ });
+
+ if (apn != contextMap.end()) {
+ return apn->second;
+ }
+
+ return std::nullopt;
+ }
+
+ at::Result::Code PacketData::setAPN(std::shared_ptr<APN::Config> apn)
+ {
+ contextMap[apn->contextId] = apn;
+ return updateAPNSettings(apn->contextId);
+ }
+
+ bool PacketData::setDataTransfer(DataTransfer dt)
+ {
+ dataTransfer = dt;
+ setupAPNSettings();
+ return true;
+ }
+ DataTransfer PacketData::getDataTransfer() const
+ {
+ return dataTransfer;
+ }
+
+ std::optional<const std::vector<std::shared_ptr<APN::Config>>> PacketData::getActiveContexts()
+ {
+ PDPContext pdpCtx(cellularService);
+ return pdpCtx.getActive();
+ }
+ at::Result::Code PacketData::activateContext(std::uint8_t contextId)
+ {
+ PDPContext pdpCtx(cellularService);
+ return pdpCtx.activate(contextId);
+ }
+ at::Result::Code PacketData::deactivateContext(std::uint8_t contextId)
+ {
+ PDPContext pdpCtx(cellularService);
+ return pdpCtx.deactivate(contextId);
+ }
+} // namespace packet_data
A module-services/service-cellular/PacketData.hpp => module-services/service-cellular/PacketData.hpp +104 -0
@@ 0,0 1,104 @@
+// 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 <string>
+#include <unordered_map>
+#include "PacketData.hpp"
+
+#include "service-cellular/PacketDataTypes.hpp"
+#include "service-cellular/ServiceCellular.hpp"
+
+namespace at
+{
+ namespace response
+ {
+ /// Separate, specific AT command for quectel, only for TCP/PDP connection/configuration
+
+ bool parseQICSGP(const at::Result &resp, std::shared_ptr<packet_data::APN::Config> retAPN);
+ bool parseQIACT(const at::Result &resp, std::vector<std::shared_ptr<packet_data::APN::Config>> &ret);
+
+ } // namespace response
+ namespace query
+ {
+ std::string prepareQICSGP(std::shared_ptr<packet_data::APN::Config> apn, bool setEmpty = false);
+ }
+} // namespace at
+
+namespace packet_data
+{
+
+ class PDPContext
+ {
+
+ private:
+ ServiceCellular &cellularService;
+ DLC_channel *channel = nullptr;
+
+ public:
+ explicit PDPContext(ServiceCellular &cellularService);
+ at::Result::Code setConfiguration(std::shared_ptr<APN::Config> apn, bool setEmpty = false);
+ std::shared_ptr<APN::Config> getConfiguration(std::uint8_t contextId);
+ at::Result::Code activate(std::uint8_t contextId);
+ at::Result::Code deactivate(std::uint8_t contextId);
+ std::optional<const std::vector<std::shared_ptr<APN::Config>>> getActive();
+ };
+
+ /**
+ * @brief wrapping class, transfer data (APN) settings. Responsible for maintaining the state
+ * between the modem and the settings
+ */
+ class PacketData
+ {
+ private:
+ ServiceCellular &cellularService;
+ ContextMap contextMap;
+ DataTransfer dataTransfer = DataTransfer::On; /// depend on APN settings
+
+ public:
+ explicit PacketData(ServiceCellular &cellularService);
+ /**
+ * @brief load APN settings from phone memory, not call modem func.
+ *
+ * To synchronize with modem, this function should be call as soon as
+ * the modem is ready (in sense of AT commands). Then should be call
+ * setupAPNSettings to synchronize modem database with phone database.
+ */
+ void loadAPNSettings();
+
+ /**
+ * @brief save all APN's in phone memory
+ */
+ void saveAPNSettings();
+
+ /**
+ * @brief setup APN on modem, based on configuration loaded in loadAPNSettings
+ * cleanup old configuration
+ */
+ void setupAPNSettings();
+
+ at::Result::Code updateAPNSettings(std::uint8_t ctxId);
+
+ std::optional<const std::vector<std::shared_ptr<APN::Config>>> getActiveContexts();
+ at::Result::Code activateContext(std::uint8_t contextId);
+ at::Result::Code deactivateContext(std::uint8_t contextId);
+
+ bool setDataTransfer(DataTransfer dt);
+ DataTransfer getDataTransfer() const;
+ /**
+ * @brief get one APN from phone configuration, connected with ctxid
+ */
+ std::optional<std::shared_ptr<APN::Config>> getAPN(std::uint8_t ctxid);
+ /**
+ * @brief get all APNs from phone configuration
+ */
+ const std::vector<std::shared_ptr<APN::Config>> getAPNs() const;
+ /**
+ * @brief get first APN with type, from phone configuration
+ */
+ std::optional<std::shared_ptr<APN::Config>> getAPNFirst(APN::APNType type);
+ at::Result::Code setAPN(std::shared_ptr<APN::Config> apn);
+ };
+
+} // namespace packet_data
M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +110 -1
@@ 102,6 102,8 @@ const char *State::c_str(State::ST state)
return "PowerUpProcedure";
case ST::AudioConfigurationProcedure:
return "AudioConfigurationProcedure";
+ case ST::APNConfProcedure:
+ return "APNConfProcedure";
case ST::ModemOn:
return "ModemOn";
case ST::URCReady:
@@ 196,6 198,9 @@ ServiceCellular::ServiceCellular() : sys::Service(serviceName, "", cellularStack
sys::Bus::SendMulticast(msg.value(), sys::BusChannels::ServiceCellularNotifications, this);
};
registerMessageHandlers();
+
+ packetData = std::make_unique<packet_data::PacketData>(*this);
+ packetData->loadAPNSettings();
}
ServiceCellular::~ServiceCellular()
@@ 259,8 264,10 @@ sys::ReturnCodes ServiceCellular::SwitchPowerModeHandler(const sys::ServicePower
return sys::ReturnCodes::Success;
}
+
void handleCellularSimNewPinDataMessage(CellularSimNewPinDataMessage *msg)
{}
+
void ServiceCellular::registerMessageHandlers()
{
connect(typeid(CellularSimNewPinDataMessage), [&](sys::Message *request) -> sys::MessagePointer {
@@ 287,6 294,40 @@ void ServiceCellular::registerMessageHandlers()
auto msg = static_cast<CellularStartOperatorsScanMessage *>(request);
return handleCellularStartOperatorsScan(msg);
});
+ connect(typeid(CellularGetActiveContextsMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularGetActiveContextsMessage *>(request);
+ return handleCellularGetActiveContextsMessage(msg);
+ });
+
+ connect(typeid(CellularGetAPNMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularGetAPNMessage *>(request);
+ return handleCellularGetAPNMessage(msg);
+ });
+
+ connect(typeid(CellularSetDataTransferMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularSetDataTransferMessage *>(request);
+ return handleCellularSetDataTransferMessage(msg);
+ });
+
+ connect(typeid(CellularGetDataTransferMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularGetDataTransferMessage *>(request);
+ return handleCellularGetDataTransferMessage(msg);
+ });
+
+ connect(typeid(CellularActivateContextMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularActivateContextMessage *>(request);
+ return handleCellularActivateContextMessage(msg);
+ });
+
+ connect(typeid(CellularDeactivateContextMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularDeactivateContextMessage *>(request);
+ return handleCellularDeactivateContextMessage(msg);
+ });
+
+ connect(typeid(CellularGetActiveContextsMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularGetActiveContextsMessage *>(request);
+ return handleCellularGetActiveContextsMessage(msg);
+ });
handle_CellularGetChannelMessage();
}
@@ 342,6 383,9 @@ void ServiceCellular::change_state(cellular::StateChange *msg)
case State::ST::CellularConfProcedure:
handle_start_conf_procedure();
break;
+ case State::ST::APNConfProcedure:
+ handle_apn_conf_procedure();
+ break;
case State::ST::SanityCheck:
handle_sim_sanity_check();
break;
@@ 531,7 575,7 @@ bool ServiceCellular::handle_audio_conf_procedure()
LOG_DEBUG("Setting up notifications callback");
notificationsChannel->setCallback(notificationCallback);
}
- state.set(this, State::ST::SanityCheck);
+ state.set(this, State::ST::APNConfProcedure);
return true;
}
else {
@@ 1991,3 2035,68 @@ std::shared_ptr<cellular::RawCommandRespAsync> ServiceCellular::handleCellularSt
sys::Bus::SendUnicast(ret, msg->sender, this);
return ret;
}
+bool ServiceCellular::handle_apn_conf_procedure()
+{
+ LOG_DEBUG("APN on modem configuration");
+ packetData->setupAPNSettings();
+ state.set(this, State::ST::SanityCheck);
+ return true;
+}
+
+std::shared_ptr<CellularGetAPNResponse> ServiceCellular::handleCellularGetAPNMessage(CellularGetAPNMessage *msg)
+{
+ std::vector<std::shared_ptr<packet_data::APN::Config>> apns;
+
+ if (auto type = msg->getAPNType(); type) {
+ if (auto apn = packetData->getAPNFirst(*type); apn) {
+ apns.push_back(*apn);
+ }
+ return std::make_shared<CellularGetAPNResponse>(apns);
+ }
+
+ if (auto ctxid = msg->getContextId(); ctxid) {
+ if (auto apn = packetData->getAPN(*ctxid); apn) {
+ apns.push_back(*apn);
+ }
+ return std::make_shared<CellularGetAPNResponse>(apns);
+ }
+
+ return std::make_shared<CellularGetAPNResponse>(packetData->getAPNs());
+}
+std::shared_ptr<CellularSetAPNResponse> ServiceCellular::handleCellularSetAPNMessage(CellularSetAPNMessage *msg)
+{
+
+ auto apn = msg->getAPNConfig();
+
+ return std::make_shared<CellularSetAPNResponse>(packetData->setAPN(apn));
+}
+std::shared_ptr<CellularSetDataTransferResponse> ServiceCellular::handleCellularSetDataTransferMessage(
+ CellularSetDataTransferMessage *msg)
+{
+ packetData->setDataTransfer(msg->getDataTransfer());
+ return std::make_shared<CellularSetDataTransferResponse>(at::Result::Code::OK);
+}
+std::shared_ptr<CellularGetDataTransferResponse> ServiceCellular::handleCellularGetDataTransferMessage(
+ CellularGetDataTransferMessage *msg)
+{
+ return std::make_shared<CellularGetDataTransferResponse>(packetData->getDataTransfer());
+}
+
+std::shared_ptr<CellularActivateContextResponse> ServiceCellular::handleCellularActivateContextMessage(
+ CellularActivateContextMessage *msg)
+{
+ return std::make_shared<CellularActivateContextResponse>(packetData->activateContext(msg->getContextId()),
+ msg->getContextId());
+}
+std::shared_ptr<CellularDeactivateContextResponse> ServiceCellular::handleCellularDeactivateContextMessage(
+ CellularDeactivateContextMessage *msg)
+{
+ return std::make_shared<CellularDeactivateContextResponse>(packetData->deactivateContext(msg->getContextId()),
+ msg->getContextId());
+}
+
+std::shared_ptr<CellularGetActiveContextsResponse> ServiceCellular::handleCellularGetActiveContextsMessage(
+ CellularGetActiveContextsMessage *msg)
+{
+ return std::make_shared<CellularGetActiveContextsResponse>(packetData->getActiveContexts());
+}
M module-services/service-cellular/service-cellular/CellularServiceAPI.hpp => module-services/service-cellular/service-cellular/CellularServiceAPI.hpp +18 -0
@@ 4,6 4,7 @@
#pragma once
#include "CellularMessage.hpp"
+#include "PacketDataCellularMessage.hpp"
#include <Modem/TS0710/TS0710.h>
#include <PhoneNumber.hpp>
@@ 80,4 81,21 @@ namespace CellularServiceAPI
CellularSimCardLockDataMessage::SimCardLock lock,
const std::vector<unsigned int> &pin);
bool SetSimCard(sys::Service *serv, Store::GSM::SIM sim);
+
+ /**
+ * @brief get all APNs from phone configuration
+ */
+ bool GetAPN(sys::Service *serv);
+ /**
+ * @brief get one APN from phone configuration, connected with ctxid
+ */
+ bool GetAPN(sys::Service *serv, std::uint8_t contextId);
+ /**
+ * @brief get first APN with type, from phone configuration
+ */
+ bool GetAPN(sys::Service *serv, packet_data::APN::APNType type);
+
+ bool SetAPN(sys::Service *serv, packet_data::APN::Config apnConfig);
+ bool SetDataTransfer(sys::Service *serv, packet_data::DataTransfer dt);
+ bool GetDataTransfer(sys::Service *serv);
}; // namespace CellularServiceAPI
A module-services/service-cellular/service-cellular/PacketDataCellularMessage.hpp => module-services/service-cellular/service-cellular/PacketDataCellularMessage.hpp +224 -0
@@ 0,0 1,224 @@
+// 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 <string>
+#include <optional>
+#include "CellularMessage.hpp"
+#include "MessageType.hpp"
+
+#include "PacketDataTypes.hpp"
+#include "Result.hpp"
+
+class CellularGetAPNMessage : public CellularMessage
+{
+ std::optional<std::uint8_t> contextId = std::nullopt;
+ std::optional<packet_data::APN::APNType> apnType = std::nullopt;
+
+ public:
+ CellularGetAPNMessage() : CellularMessage(MessageType::CellularPacketData)
+ {}
+ CellularGetAPNMessage(std::uint8_t contextId)
+ : CellularMessage(MessageType::CellularPacketData), contextId(contextId)
+ {}
+ CellularGetAPNMessage(packet_data::APN::APNType apnType)
+ : CellularMessage(MessageType::CellularPacketData), apnType(apnType)
+ {}
+ [[nodiscard]] const std::optional<packet_data::APN::APNType> getAPNType() const noexcept
+ {
+ return apnType;
+ }
+ [[nodiscard]] const std::optional<std::uint8_t> getContextId() const noexcept
+ {
+ return contextId;
+ }
+};
+
+class CellularSetAPNMessage : public CellularMessage // also as DeleteAPN
+{
+ std::shared_ptr<packet_data::APN::Config> apnConfig;
+
+ public:
+ CellularSetAPNMessage(std::shared_ptr<packet_data::APN::Config> apnConfig)
+ : CellularMessage(MessageType::CellularPacketData), apnConfig(apnConfig)
+ {}
+
+ [[nodiscard]] std::shared_ptr<packet_data::APN::Config> getAPNConfig() const noexcept
+ {
+ return apnConfig;
+ }
+};
+
+class CellularSetDataTransferMessage : public CellularMessage
+{
+ packet_data::DataTransfer dataTransfer;
+
+ public:
+ CellularSetDataTransferMessage(packet_data::DataTransfer dataTransfer)
+ : CellularMessage(MessageType::CellularPacketData), dataTransfer(dataTransfer)
+ {}
+ [[nodiscard]] packet_data::DataTransfer getDataTransfer() const noexcept
+ {
+ return dataTransfer;
+ }
+};
+
+class CellularGetDataTransferMessage : public CellularMessage
+{
+ public:
+ CellularGetDataTransferMessage() : CellularMessage(MessageType::CellularPacketData)
+ {}
+};
+
+class CellularGetActiveContextsMessage : public CellularMessage
+{
+ public:
+ CellularGetActiveContextsMessage() : CellularMessage(MessageType::CellularPacketData)
+ {}
+};
+
+class CellularActivateContextMessage : public CellularMessage
+{
+ std::uint8_t contextId;
+
+ public:
+ CellularActivateContextMessage(std::uint8_t contextId)
+ : CellularMessage(MessageType::CellularPacketData), contextId(contextId)
+ {}
+ [[nodiscard]] std::uint8_t getContextId() const noexcept
+ {
+ return contextId;
+ }
+};
+class CellularDeactivateContextMessage : public CellularMessage
+{
+ std::uint8_t contextId;
+
+ public:
+ CellularDeactivateContextMessage(std::uint8_t contextId)
+ : CellularMessage(MessageType::CellularPacketData), contextId(contextId)
+ {}
+ [[nodiscard]] std::uint8_t getContextId() const noexcept
+ {
+ return contextId;
+ }
+};
+
+// Send from Cellular
+class CellularGetAPNResponse : public CellularMessage
+{
+ std::vector<std::shared_ptr<packet_data::APN::Config>> apns;
+
+ public:
+ CellularGetAPNResponse(std::vector<std::shared_ptr<packet_data::APN::Config>> apns)
+ : CellularMessage(MessageType::CellularPacketData), apns(std::move(apns))
+ {}
+
+ [[nodiscard]] const std::vector<std::shared_ptr<packet_data::APN::Config>> &getAPNs() const noexcept
+ {
+ return apns;
+ }
+};
+
+class CellularATResponse : public CellularMessage
+{
+ at::Result::Code result;
+
+ public:
+ CellularATResponse(at::Result::Code result) : CellularMessage(MessageType::CellularPacketData), result(result)
+ {}
+
+ [[nodiscard]] at::Result::Code getResult() const noexcept
+ {
+ return result;
+ }
+};
+
+class CellularSetAPNResponse : public CellularATResponse
+{
+ public:
+ CellularSetAPNResponse(at::Result::Code result) : CellularATResponse(result)
+ {}
+};
+
+class CellularSetDataTransferResponse : public CellularATResponse
+{
+ public:
+ CellularSetDataTransferResponse(at::Result::Code result) : CellularATResponse(result)
+ {}
+};
+
+class CellularGetDataTransferResponse : public CellularMessage
+{
+ packet_data::DataTransfer dataTransfer;
+
+ public:
+ CellularGetDataTransferResponse(packet_data::DataTransfer dataTransfer)
+ : CellularMessage(MessageType::CellularPacketData), dataTransfer(dataTransfer)
+ {}
+
+ [[nodiscard]] packet_data::DataTransfer getDataTransfer() const noexcept
+ {
+ return dataTransfer;
+ }
+};
+
+class CellularGetActiveContextsResponse : public CellularMessage
+{
+ std::optional<std::vector<std::shared_ptr<packet_data::APN::Config>>> result;
+
+ public:
+ CellularGetActiveContextsResponse(std::optional<std::vector<std::shared_ptr<packet_data::APN::Config>>> result)
+ : CellularMessage(MessageType::CellularPacketData)
+ {
+ if (result) {
+ result = std::move(*result);
+ }
+ else {
+ result = std::nullopt;
+ }
+ }
+
+ [[nodiscard]] std::optional<const std::vector<std::shared_ptr<packet_data::APN::Config>>> getActive()
+ {
+ return result;
+ }
+};
+
+class CellularActivateContextResponse : public CellularATResponse
+{
+ std::uint8_t contextId;
+
+ public:
+ CellularActivateContextResponse(at::Result::Code result, std::uint8_t contextId)
+ : CellularATResponse(result), contextId(contextId)
+ {}
+ [[nodiscard]] std::uint8_t getContextId() const noexcept
+ {
+ return contextId;
+ }
+};
+class CellularDeactivateContextResponse : public CellularATResponse
+{
+ std::uint8_t contextId;
+ bool isUrc;
+
+ public:
+ CellularDeactivateContextResponse(at::Result::Code result, std::uint8_t contextId, bool isUrc = false)
+ : CellularATResponse(result), contextId(contextId), isUrc(isUrc)
+ {}
+
+ [[nodiscard]] std::uint8_t getContextId() const noexcept
+ {
+ return contextId;
+ }
+ /**
+ * @breif inform that message come from URC (eg. because call AT+CFUN=0, reset by BTS, ...)
+ * @return
+ */
+ [[nodiscard]] bool isURC()
+ {
+ return isUrc;
+ }
+};
A module-services/service-cellular/service-cellular/PacketDataTypes.hpp => module-services/service-cellular/service-cellular/PacketDataTypes.hpp +117 -0
@@ 0,0 1,117 @@
+// 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 <string>
+#include <memory>
+
+namespace packet_data
+{
+
+ enum class DataTransfer
+ {
+ Off,
+ On
+ };
+
+ namespace APN
+ {
+ /**
+ * @brief Defines APN types
+ */
+ enum class APNType
+ {
+ Default, ///< for data traffic
+ IMS, ///< IP Multimedia Subsystem for eg VoLTE
+ MMS, ///< for MMS service
+ Fota ///< for Firmware Update
+ };
+
+ /**
+ * @brief Defines ip address type as in "TCP(IP) AT Commands Manual v1.1" from 2019-10-28
+ */
+ enum class ContextType : unsigned char
+ {
+ ipv4 = 1,
+ ipv6 = 2, ///< note, 2 was for ipv4v6 in "TCP(IP) AT Commands Manual v1.0"
+ ipv4v6 = 3
+ };
+
+ /**
+ * @brief APN Authorization method
+ */
+ enum class AuthMethod : unsigned char
+ {
+ NONE = 0,
+ PAP = 1,
+ CHAP = 2,
+ AUTO = 3,
+ };
+
+ /**
+ * @brief To configure whether to save <username> and <password> over CDMA network.
+ */
+ enum class SaveCDMAPwd : unsigned char
+ {
+ Disable = 0,
+ Enable = 1
+ };
+
+ enum class ContextState : unsigned char
+ {
+ Deactivated = 0,
+ Activated = 1
+ };
+
+ /**
+ * @brief APN configuration data
+ */
+ class Config
+ {
+ public:
+ unsigned char contextId =
+ 0; /// context on which apn is configured available values 1-16, 0 - means not set yet
+ APNType apnType = APNType::Default;
+ ContextState state = ContextState::Deactivated;
+ ContextType contextType = ContextType::ipv4; /// IP type
+ AuthMethod authMethod = AuthMethod::NONE;
+ std::string apn; /// name of the APN
+ std::string username;
+ std::string password;
+ std::string ip; /// set after connection
+
+ bool isEmpty() const noexcept
+ {
+ return apn.empty();
+ }
+
+ bool compare(std::shared_ptr<Config> c2)
+ {
+ return (this->apn == c2->apn) && (this->contextId == c2->contextId) &&
+ (this->contextType == c2->contextType) && (this->password == c2->password) &&
+ (this->username == c2->username) && (this->authMethod == c2->authMethod);
+ }
+ /**
+ * @brief Compare two APNs only by elements essential for modem
+ */
+ friend bool operator==(const Config &c1, const Config &c2)
+ {
+ return (c1.apn == c2.apn) && (c1.contextId == c2.contextId) && (c1.contextType == c2.contextType) &&
+ (c1.password == c2.password) && (c1.username == c2.username) && (c1.authMethod == c2.authMethod);
+ }
+
+ friend bool operator!=(const Config &c1, const Config &c2)
+ {
+ return !(c1 == c2);
+ }
+ };
+
+ } // namespace APN
+
+ constexpr unsigned char MINContextId = 1;
+ constexpr unsigned char MAXContextId = 16;
+ using ContextMap = std::unordered_map<unsigned char, std::shared_ptr<APN::Config>>;
+ using ContextPair = std::pair<unsigned char, std::shared_ptr<APN::Config>>;
+
+} // namespace packet_data
M module-services/service-cellular/service-cellular/ServiceCellular.hpp => module-services/service-cellular/service-cellular/ServiceCellular.hpp +24 -0
@@ 9,6 9,8 @@
#include "CellularMessage.hpp"
#include "State.hpp"
#include "USSD.hpp"
+#include "PacketData.hpp"
+#include "PacketDataCellularMessage.hpp"
#include <Modem/TS0710/DLC_channel.h>
#include <Modem/TS0710/TS0710.h>
@@ 46,6 48,11 @@ namespace sys
class Timer;
} // namespace sys
+namespace packet_data
+{
+ class PacketData;
+ class PDPContext;
+} // namespace packet_data
class ServiceCellular : public sys::Service
{
@@ 159,6 166,8 @@ class ServiceCellular : public sys::Service
void CallStateTimerHandler();
DLC_channel::Callback_t notificationCallback = nullptr;
+ std::unique_ptr<packet_data::PacketData> packetData;
+
cellular::State state;
bsp::Board board = bsp::Board::none;
@@ 221,6 230,7 @@ class ServiceCellular : public sys::Service
bool handle_fatal_failure();
bool handle_ready();
std::unique_ptr<settings::Settings> settings = std::make_unique<settings::Settings>(this);
+ bool handle_apn_conf_procedure();
bool handleAllMessagesFromMessageStorage();
[[nodiscard]] SMSRecord createSMSRecord(const UTF8 &decodedMessage,
@@ 260,8 270,22 @@ class ServiceCellular : public sys::Service
std::shared_ptr<cellular::RawCommandRespAsync> handleCellularStartOperatorsScan(
CellularStartOperatorsScanMessage *msg);
+ std::shared_ptr<CellularGetAPNResponse> handleCellularGetAPNMessage(CellularGetAPNMessage *msg);
+ std::shared_ptr<CellularSetAPNResponse> handleCellularSetAPNMessage(CellularSetAPNMessage *msg);
+ std::shared_ptr<CellularSetDataTransferResponse> handleCellularSetDataTransferMessage(
+ CellularSetDataTransferMessage *msg);
+ std::shared_ptr<CellularGetDataTransferResponse> handleCellularGetDataTransferMessage(
+ CellularGetDataTransferMessage *msg);
+ std::shared_ptr<CellularActivateContextResponse> handleCellularActivateContextMessage(
+ CellularActivateContextMessage *msg);
+ std::shared_ptr<CellularDeactivateContextResponse> handleCellularDeactivateContextMessage(
+ CellularDeactivateContextMessage *msg);
+ std::shared_ptr<CellularGetActiveContextsResponse> handleCellularGetActiveContextsMessage(
+ CellularGetActiveContextsMessage *msg);
friend class CellularUrcHandler;
friend class SimCard;
friend class CellularRequestHandler;
friend class NetworkSettings;
+ friend class packet_data::PDPContext;
+ friend class packet_data::PacketData;
};
M module-services/service-cellular/service-cellular/State.hpp => module-services/service-cellular/service-cellular/State.hpp +1 -0
@@ 18,6 18,7 @@ namespace cellular
PowerUpInProgress, /// waiting for modem powered up by polling various bauds
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
M module-services/service-cellular/tests/CMakeLists.txt => module-services/service-cellular/tests/CMakeLists.txt +9 -0
@@ 18,3 18,12 @@ add_catch2_executable(
module-cellular
)
+
+add_catch2_executable(
+ NAME
+ cellular-datatransfer
+ SRCS
+ unittest_datatransfer.cpp
+ LIBS
+ module-cellular
+)<
\ No newline at end of file
A module-services/service-cellular/tests/unittest_datatransfer.cpp => module-services/service-cellular/tests/unittest_datatransfer.cpp +96 -0
@@ 0,0 1,96 @@
+// 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 "Result.hpp"
+#include <service-cellular/PacketData.hpp>
+#include <service-cellular/PacketDataTypes.hpp>
+
+using namespace cellular;
+
+TEST_CASE("Quectel AT DataTransfer commands")
+{
+ SECTION("QIACT")
+ {
+ std::vector<std::shared_ptr<packet_data::APN::Config>> ret;
+ at::Result resp;
+
+ // OK
+ resp.code = at::Result::Code::OK;
+ resp.response.push_back("+QIACT: 1,1,1,\"10.7.157.1\"");
+ REQUIRE(resp.code == at::Result::Code::OK);
+ REQUIRE(at::response::parseQIACT(resp, ret) == true);
+ REQUIRE(ret.size() == 1);
+ REQUIRE(ret.at(0)->ip == "10.7.157.1");
+
+ ret.clear();
+ // Additional element, should be omitted, also other wrong data
+ resp.response.push_back("+QIACT:");
+ REQUIRE(at::response::parseQIACT(resp, ret) == true);
+ REQUIRE(ret.size() == 1);
+ REQUIRE(ret.at(0)->ip == "10.7.157.1");
+
+ ret.clear();
+ resp.response.clear();
+ // Empty return, should return empty list
+ REQUIRE(at::response::parseQIACT(resp, ret) == true);
+ REQUIRE(ret.size() == 0);
+
+ ret.clear();
+ resp.response.clear();
+ // Wrong should return zero
+ resp.response.push_back("+QIACT: sa sa+QIACT: fsf fsa");
+ REQUIRE(at::response::parseQIACT(resp, ret) == true);
+ REQUIRE(ret.size() == 0);
+ }
+ SECTION("QICSGP")
+ {
+ /// +QICSGP: 1,"","","",0
+ std::shared_ptr<packet_data::APN::Config> ret = std::make_shared<packet_data::APN::Config>();
+ at::Result resp;
+ resp.code = at::Result::Code::OK;
+ // OK
+ resp.response.push_back("+QICSGP: 1,\"apn\",\"\",\"\",0");
+ REQUIRE(resp.code == at::Result::Code::OK);
+ REQUIRE(at::response::parseQICSGP(resp, ret) == true);
+ REQUIRE(ret->apn == "apn");
+
+ resp.response.clear();
+ // OK
+ resp.response.push_back("+QICSGP: 1,\"apn\",\"internet\",\"password\",1");
+ REQUIRE(resp.code == at::Result::Code::OK);
+ REQUIRE(at::response::parseQICSGP(resp, ret) == true);
+ REQUIRE(ret->apn == "apn");
+ REQUIRE(ret->username == "internet");
+ REQUIRE(ret->password == "password");
+
+ resp.response.clear();
+ // empty APN
+ resp.response.push_back("+QICSGP: 1,\"\",\"\",\"\",0");
+ REQUIRE(at::response::parseQICSGP(resp, ret) == true);
+ REQUIRE(ret->apn == "");
+ REQUIRE(ret->isEmpty() == true);
+
+ // bad response
+ resp.code = at::Result::Code::ERROR;
+ REQUIRE(at::response::parseQICSGP(resp, ret) == false);
+
+ resp.response.clear();
+ // Bad token
+ resp.response.push_back("+QICSXX: 1,\"\",\"\",\"\",0");
+ REQUIRE(at::response::parseQICSGP(resp, ret) == false);
+
+ resp.response.clear();
+ // Not enought parameters
+ resp.response.push_back("+QICSGP: 1,\"\",\"\"");
+ REQUIRE(at::response::parseQICSGP(resp, ret) == false);
+
+ resp.response.clear();
+ // wrong msg
+ resp.response.push_back("ds +QICSGP: adsad +QICSGP: 1,\"\",\"");
+ REQUIRE(at::response::parseQICSGP(resp, ret) == false);
+ }
+}
M module-services/service-fota/FotaUrcHandler.hpp => module-services/service-fota/FotaUrcHandler.hpp +1 -0
@@ 26,6 26,7 @@ class FotaUrcHandler : public UrcHandler
virtual void Handle(Cusd &urc){};
virtual void Handle(Ctze &urc){};
virtual void Handle(Cpin &urc){};
+ virtual void Handle(Qiurc &urc){};
virtual void Handle(PoweredDown &urc){};
virtual void Handle(UrcResponse &urc){};
M source/MessageType.hpp => source/MessageType.hpp +2 -0
@@ 80,6 80,8 @@ enum class MessageType
CellularSimResponse, // Send to PIN window (show, error state, hide)
CellularSimVerifyPinRequest, // Send from PIN window with PIN, PUK, ... number
+ CellularPacketData, ///< for all PacketData messages
+
CellularGetOwnNumber,
CellularGetIMSI,
CellularGetNetworkInfo,