M changelog.md => changelog.md +1 -0
@@ 22,6 22,7 @@
* `[gui][desktop]` ScreenLock logic unified with simLock's
* `[messages]` Changed fonts of message snippet and its prefix.
+* `[cellular]` Improve MMI call forwarding support
## [0.47.1 2020-11-20]
M module-cellular/at/src/UrcClip.cpp => module-cellular/at/src/UrcClip.cpp +1 -4
@@ 24,10 24,7 @@ std::optional<Clip::AddressType> Clip::getType() const
return std::nullopt;
}
- int addressType;
- if (!utils::toNumeric(tokens[magic_enum::enum_integer(Tokens::Type)], addressType)) {
- return std::nullopt;
- }
+ auto addressType = utils::getNumericValue<int>(tokens[magic_enum::enum_integer(Tokens::Type)]);
constexpr auto addressTypes = magic_enum::enum_values<Clip::AddressType>();
for (const auto &type : addressTypes) {
M module-cellular/at/src/UrcCtze.cpp => module-cellular/at/src/UrcCtze.cpp +2 -7
@@ 18,15 18,10 @@ int Ctze::getTimeZoneOffset() const
{
const std::string &tzOffsetToken = tokens[static_cast<uint32_t>(Tokens::GMTDifference)];
- int offsetInQuartersOfHour = 0;
- bool failed = !utils::toNumeric(tzOffsetToken, offsetInQuartersOfHour);
+ auto offsetInQuartersOfHour = utils::getNumericValue<int>(tzOffsetToken);
if (offsetInQuartersOfHour != std::clamp(offsetInQuartersOfHour, minTimezoneOffset, maxTimezoneOffset)) {
offsetInQuartersOfHour = 0;
- failed = true;
- }
-
- if (failed) {
LOG_ERROR("Failed to parse Ctze time zone offset: %s", tzOffsetToken.c_str());
}
@@ 69,7 64,7 @@ auto Ctze::getTimeInfo() const noexcept -> tm
auto gmtDifferenceStr = tokens[Tokens::GMTDifference];
- int gmtDifference = utils::getValue<int>(gmtDifferenceStr);
+ int gmtDifference = utils::getNumericValue<int>(gmtDifferenceStr);
auto time = system_clock::to_time_t(tp) +
gmtDifference * utils::time::minutesInQuarterOfHour * utils::time::secondsInMinute;
timeinfo = *gmtime(&time);
M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +10 -7
@@ 158,7 158,8 @@ static void ExtVibrationStop()
bool ServiceAudio::IsVibrationEnabled(const audio::PlaybackType &type)
{
- auto isEnabled = utils::getValue<audio::Vibrate>(getSetting(Setting::EnableVibration, Profile::Type::Idle, type));
+ auto isEnabled =
+ utils::getNumericValue<audio::Vibrate>(getSetting(Setting::EnableVibration, Profile::Type::Idle, type));
return isEnabled;
}
bool ServiceAudio::IsOperationEnabled(const audio::PlaybackType &plType, const Operation::Type &opType)
@@ 166,7 167,8 @@ bool ServiceAudio::IsOperationEnabled(const audio::PlaybackType &plType, const O
if (opType == Operation::Type::Router || opType == Operation::Type::Recorder) {
return true;
}
- auto isEnabled = utils::getValue<audio::EnableSound>(getSetting(Setting::EnableSound, Profile::Type::Idle, plType));
+ auto isEnabled =
+ utils::getNumericValue<audio::EnableSound>(getSetting(Setting::EnableSound, Profile::Type::Idle, plType));
return isEnabled;
}
@@ 442,7 444,8 @@ auto ServiceAudio::HandleKeyPressed(const int step) -> std::unique_ptr<AudioKeyP
return std::make_unique<AudioKeyPressedResponse>(audio::RetCode::Success, 0, muted, context);
}
}
- const auto volume = utils::getValue<int>(getSetting(Setting::Volume, Profile::Type::Idle, PlaybackType::None));
+ const auto volume =
+ utils::getNumericValue<int>(getSetting(Setting::Volume, Profile::Type::Idle, PlaybackType::None));
const auto newVolume = std::clamp(volume + step, static_cast<int>(minVolume), static_cast<int>(maxVolume));
setSetting(Setting::Volume, std::to_string(newVolume), Profile::Type::Idle, PlaybackType::None);
return std::make_unique<AudioKeyPressedResponse>(audio::RetCode::Success, newVolume, muted, context);
@@ 473,7 476,7 @@ sys::MessagePointer ServiceAudio::DataReceivedHandler(sys::DataMessage *msgl, sy
else if (msgType == typeid(AudioGetSetting)) {
auto *msg = static_cast<AudioGetSetting *>(msgl);
auto value = getSetting(msg->setting, msg->profileType, msg->playbackType);
- responseMsg = std::make_shared<AudioResponseMessage>(RetCode::Success, utils::getValue<float>(value));
+ responseMsg = std::make_shared<AudioResponseMessage>(RetCode::Success, utils::getNumericValue<float>(value));
}
else if (msgType == typeid(AudioSetSetting)) {
auto *msg = static_cast<AudioSetSetting *>(msgl);
@@ 612,7 615,7 @@ void ServiceAudio::setSetting(const Setting &setting,
else if (auto input = audioMux.GetIdleInput(); input && (setting == audio::Setting::Volume)) {
updatedProfile = (*input)->audio->GetPriorityPlaybackProfile();
updatedPlayback = PlaybackType::CallRingtone;
- valueToSet = std::clamp(utils::getValue<audio::Volume>(value), minVolume, maxVolume);
+ valueToSet = std::clamp(utils::getNumericValue<audio::Volume>(value), minVolume, maxVolume);
}
else {
return;
@@ 621,14 624,14 @@ void ServiceAudio::setSetting(const Setting &setting,
switch (setting) {
case Setting::Volume: {
- const auto clampedValue = std::clamp(utils::getValue<audio::Volume>(value), minVolume, maxVolume);
+ const auto clampedValue = std::clamp(utils::getNumericValue<audio::Volume>(value), minVolume, maxVolume);
valueToSet = std::to_string(clampedValue);
if (activeInput) {
retCode = activeInput.value()->audio->SetOutputVolume(clampedValue);
}
} break;
case Setting::Gain: {
- const auto clampedValue = std::clamp(utils::getValue<audio::Gain>(value), minGain, maxGain);
+ const auto clampedValue = std::clamp(utils::getNumericValue<audio::Gain>(value), minGain, maxGain);
valueToSet = std::to_string(clampedValue);
if (activeInput) {
retCode = activeInput.value()->audio->SetInputGain(clampedValue);
M module-services/service-cellular/RequestFactory.cpp => module-services/service-cellular/RequestFactory.cpp +0 -1
@@ 74,7 74,6 @@ namespace cellular
catch (const std::runtime_error &except) {
LOG_ERROR("Failed to create MMI request. Error message:\n%s", except.what());
}
- break;
}
}
return std::make_unique<CallRequest>(request);
M module-services/service-cellular/requests/CallForwardingRequest.cpp => module-services/service-cellular/requests/CallForwardingRequest.cpp +30 -8
@@ 37,13 37,26 @@ namespace cellular
auto CallForwardingRequest::command() -> std::string
{
- std::vector<commandBuilderFunc> commandParts = {
- [this]() { return getCommandReason(); },
- [this]() { return getCommandMode(); },
- [this]() { return getCommandNumber(); },
- };
+ std::vector<commandBuilderFunc> commandParts = {[this]() { return getCommandReason(); },
+ [this]() { return getCommandMode(); },
+ [this]() { return getCommandNumber(); }};
- return buildCommand(at::AT::CCFC, commandParts);
+ bool trimEmpty = true;
+
+ if (!getCommandClass().empty()) {
+ commandParts.emplace_back([this]() { return getCommandType(); });
+ commandParts.emplace_back([this]() { return getCommandClass(); });
+ trimEmpty = false;
+ }
+
+ if (!getCommandTime().empty()) {
+ commandParts.emplace_back([this]() { return getCommandSubAddr(); });
+ commandParts.emplace_back([this]() { return getCommandSatype(); });
+ commandParts.emplace_back([this]() { return getCommandTime(); });
+ trimEmpty = false;
+ }
+
+ return buildCommand(at::AT::CCFC, commandParts, trimEmpty);
}
auto CallForwardingRequest::getCommandReason() const -> std::string
@@ 65,10 78,10 @@ namespace cellular
{
// according to EC25&EC21_AT_Commands_Manual_V1.3
if (auto pos = phoneNumber.find("+"); pos != std::string::npos) {
- return addressFormatTypeDefault;
+ return addressFormatTypeInternational;
}
else {
- return addressFormatTypeInternational;
+ return addressFormatTypeDefault;
}
}
@@ 96,6 109,15 @@ namespace cellular
return noReplyConditionTimer;
}
+ auto CallForwardingRequest::isValid() const noexcept -> bool
+ {
+ if (noReplyConditionTimer.empty()) {
+ return true;
+ }
+ auto time = utils::getNumericValue<int>(noReplyConditionTimer);
+ return std::clamp(time, minNoReplyTime, maxNoReplyTime) == time;
+ }
+
void CallForwardingRequest::handle(RequestHandler &h, at::Result &result)
{
h.handle(*this, result);
M module-services/service-cellular/requests/Request.cpp => module-services/service-cellular/requests/Request.cpp +7 -4
@@ 31,18 31,21 @@ namespace cellular
return true;
}
- std::string Request::buildCommand(at::AT atCommand, const std::vector<commandBuilderFunc> &builderFunctions) const
+ std::string Request::buildCommand(at::AT atCommand,
+ const std::vector<commandBuilderFunc> &builderFunctions,
+ bool trim) const
{
if (!isValid()) {
return std::string();
}
std::string cmd(at::factory(atCommand));
- bool formatFirst = true;
+
+ auto formatFirst = true;
for (auto &cmdPart : builderFunctions) {
auto partStr = cmdPart();
- if (partStr.empty()) {
- continue;
+ if (partStr.empty() && trim) {
+ break;
}
cmd.append(formatFirst ? partStr : "," + partStr);
formatFirst = false;
M module-services/service-cellular/requests/SupplementaryServicesRequest.cpp => module-services/service-cellular/requests/SupplementaryServicesRequest.cpp +1 -2
@@ 74,7 74,6 @@ namespace cellular
auto SupplementaryServicesRequest::getCommandInformationClass(const std::string &basicServiceGroup) const
-> std::optional<std::string>
{
- // according to EC25&EC21_AT_Commands_Manual_V1.3
int basicGroup = 0;
int informationClass = 0;
@@ 83,7 82,7 @@ namespace cellular
informationClass = atInformationClassAllTele + atInformationClassAllBearer;
}
else {
- utils::toNumeric(basicServiceGroup, basicGroup);
+ basicGroup = utils::getNumericValue<int>(basicServiceGroup);
auto service = magic_enum::enum_cast<TeleAndBearerService>(basicGroup);
if (!service.has_value()) {
M module-services/service-cellular/service-cellular/requests/CallForwardingRequest.hpp => module-services/service-cellular/service-cellular/requests/CallForwardingRequest.hpp +6 -2
@@ 20,16 20,20 @@ namespace cellular
auto command() -> std::string final;
void handle(RequestHandler &h, at::Result &result) final;
+ auto isValid() const noexcept -> bool final;
private:
+ // source: EC25&EC21_AT_Commands_Manual
static constexpr auto addressFormatTypeInternational = "145";
static constexpr auto addressFormatTypeDefault = "129";
- static constexpr auto subaddrDefault = "128";
+ static constexpr auto subaddrDefault = "";
+ static constexpr auto maxNoReplyTime = 30;
+ static constexpr auto minNoReplyTime = 0;
std::string forwardReason;
std::string &phoneNumber = supplementaryInfoA;
std::string &basicServiceGroup = supplementaryInfoB;
- std::string &noReplyConditionTimer = supplementaryInfoB;
+ std::string &noReplyConditionTimer = supplementaryInfoC;
// command decomposition according to EC25&EC21_AT_Commands_Manual_V1.3
auto getCommandReason() const -> std::string;
M module-services/service-cellular/service-cellular/requests/Request.hpp => module-services/service-cellular/service-cellular/requests/Request.hpp +4 -2
@@ 40,10 40,12 @@ namespace cellular
* Helper command for building output command
* @param atCommand
* @param builderFunctions functions that build parts of the output command in order of execution
+ * @param trim true to avoid appending commands that evaluate to empty string
* @return formatted command or empty string if input is invalid
*/
- auto buildCommand(at::AT atCommand, const std::vector<commandBuilderFunc> &builderFunctions) const
- -> std::string;
+ auto buildCommand(at::AT atCommand,
+ const std::vector<commandBuilderFunc> &builderFunctions,
+ bool trim = true) const -> std::string;
bool isRequestHandled = false;
std::string request;
};
M module-services/service-cellular/tests/unittest_mmi.cpp => module-services/service-cellular/tests/unittest_mmi.cpp +27 -18
@@ 29,6 29,9 @@ TEST_CASE("MMI requests")
};
std::vector<TestCase> testCases = {
+ /// USSD
+ {R"(*100*#)", R"(AT+CUSD=1,*100*#,15)", typeid(UssdRequest)},
+
/// ImeiRequest
{R"(*#06#)", R"(AT+GSN)", typeid(ImeiRequest)},
@@ 85,18 88,9 @@ TEST_CASE("MMI requests")
{R"(*#33*1111*12#)", R"(AT+CLCK="AO",2,"1111",12)", typeid(CallBarringRequest)}, // query with pass and BS
{R"(**33#)", std::string(), typeid(CallBarringRequest), false}, // bad procedure - register
{R"(##33#)", std::string(), typeid(CallBarringRequest), false}, // bad procedure - erasure
- {R"(*#33*1111*17#)",
- std::string(),
- typeid(CallBarringRequest),
- false}, // unsupported BS - Voice Group Call Service
- {R"(*#33*1111*18#)",
- std::string(),
- typeid(CallBarringRequest),
- false}, // unsupported BS - Voice Broadcast Service
- {R"(*#33*1111*99#)",
- std::string(),
- typeid(CallBarringRequest),
- false}, // unsupported BS - All GPRS bearer services
+ {R"(*#33*1111*17#)", std::string(), typeid(CallBarringRequest), false}, // unsupported BS - Voice Group Call
+ {R"(*#33*1111*18#)", std::string(), typeid(CallBarringRequest), false}, // unsupported BS - Voice Broadcast
+ {R"(*#33*1111*99#)", std::string(), typeid(CallBarringRequest), false}, // unsupported BS - All GPRS bearer
{R"(*#33*1111*45#)", std::string(), typeid(CallBarringRequest), false}, // unsupported BS - random
/// BOIC (Bar Outgoing International Calls)
{R"(*331#)", R"(AT+CLCK="OI",1)", typeid(CallBarringRequest)}, // lock
@@ 251,6 245,14 @@ TEST_CASE("MMI requests")
{R"(*#67#)", R"(AT+CCFC=1,2)", typeid(CallForwardingRequest)},
// alternative register and on
{R"(*21*666555444#)", R"(AT+CCFC=0,1,"666555444")", typeid(CallForwardingRequest)},
+ // optional parameters - basic service group
+ {R"(*21*666555444*16#)", R"(AT+CCFC=0,1,"666555444",129,8)", typeid(CallForwardingRequest)},
+ {R"(*21*+48666555444*19#)", R"(AT+CCFC=0,1,"+48666555444",145,5)", typeid(CallForwardingRequest)},
+ // optional parameters - time
+ {R"(*21*666555444*16*30#)", R"(AT+CCFC=0,1,"666555444",129,8,,,30)", typeid(CallForwardingRequest)},
+ {R"(*21*+48666555444*19*20#)", R"(AT+CCFC=0,1,"+48666555444",145,5,,,20)", typeid(CallForwardingRequest)},
+ // not valid - timeout exceeds maximum
+ {R"(*21*+48666555444*19*40#)", std::string(), typeid(CallForwardingRequest), false},
/// PasswordRegistrationRequest
// total incoming and outgoing service barring (empty string)
@@ 300,13 302,13 @@ TEST_CASE("MMI requests")
// Change PIN by PUK more than 4
{R"(**042*00002*11113*11113#)", R"(AT+CPWD="P2","00002","11113")", typeid(PinChangeRequest)},
// bad procedure type *
- {R"(*042*00002*11112*11112#)", std::string(), typeid(CallRequest)},
+ {R"(*042*00002*11112*11112#)", std::string(), typeid(UssdRequest)},
// bad procedure type ##
{R"(##042*00002*11112*11112#)", std::string(), typeid(CallRequest)},
// bad procedure type #
{R"(#042*00002*11112*11112#)", std::string(), typeid(CallRequest)},
// bad procedure type *#
- {R"(*#042*00002*11112*11112#)", std::string(), typeid(CallRequest)},
+ {R"(*#042*00002*11112*11112#)", std::string(), typeid(UssdRequest)},
// no password
{R"(**042*00002**#)", std::string(), typeid(PinChangeRequest), false},
// no password
@@ 315,8 317,12 @@ TEST_CASE("MMI requests")
{R"(**042**11112*#)", std::string(), typeid(PinChangeRequest), false},
// no password
{R"(**042**11112*#)", std::string(), typeid(PinChangeRequest), false},
- // password does not match
+ // password does not match
{R"(**042*0000*1111*2222#)", std::string(), typeid(PinChangeRequest), false},
+
+ /// call
+ {R"(666555777)", std::string(), typeid(CallRequest)},
+ {R"(+48666555777)", std::string(), typeid(CallRequest)},
};
for (auto &testCase : testCases) {
@@ 326,11 332,14 @@ TEST_CASE("MMI requests")
INFO("Failed on testcase: " << testCase.requestString);
REQUIRE(typeid(*request.get()).name() == testCase.expectedType.name());
REQUIRE(request->isValid() == testCase.expectedValid);
- if (typeid(*request.get()).name() != typeid(CallRequest).name()) {
- REQUIRE(requestCommand == testCase.expectedCommandString);
+ if (typeid(*request.get()).name() == typeid(CallRequest).name()) {
+ REQUIRE(requestCommand == "ATD" + testCase.requestString + ";");
+ }
+ else if (typeid(*request.get()).name() == typeid(UssdRequest).name()) {
+ REQUIRE(requestCommand == "AT+CUSD=1," + testCase.requestString + ",15");
}
else {
- REQUIRE(requestCommand == "ATD" + testCase.requestString + ";");
+ REQUIRE(requestCommand == testCase.expectedCommandString);
}
}
}
M module-utils/Utils.hpp => module-utils/Utils.hpp +14 -14
@@ 152,11 152,11 @@ namespace utils
return std::string(magic_enum::enum_name(t));
}
- /// Gets value of type T from string
+ /// Gets arithmetic value of type T from string
///
/// @param value to be converted
/// @return Value casted to type T
- template <typename T>[[nodiscard]] T getValue(const std::string &value)
+ template <typename T>[[nodiscard]] T getNumericValue(const std::string &value)
{
static_assert(std::is_arithmetic_v<T>);
if (value.empty()) {
@@ 167,6 167,18 @@ namespace utils
return ret;
}
+ static inline bool toNumeric(const std::string &text, int &value)
+ {
+ try {
+ value = std::stoi(text);
+ }
+ catch (const std::exception &e) {
+ LOG_ERROR("toNumeric exception %s", e.what());
+ return false;
+ }
+ return true;
+ }
+
static inline void findAndReplaceAll(std::string &data,
const std::vector<std::pair<std::string, std::optional<std::string>>> &values,
std::function<std::string(int)> getReplaceString = nullptr)
@@ 218,18 230,6 @@ namespace utils
findAndReplaceAll(data, {{toSearch, replaceStr}});
}
- static inline bool toNumeric(const std::string &text, int &value)
- {
- try {
- value = std::stoi(text);
- }
- catch (const std::exception &e) {
- LOG_ERROR("toNumeric exception %s", e.what());
- return false;
- }
- return true;
- }
-
static inline uint32_t swapBytes(uint32_t toSwap)
{
#ifdef __GNUC__
M module-utils/test/unittest_utils.cpp => module-utils/test/unittest_utils.cpp +2 -2
@@ 168,14 168,14 @@ TEST_CASE("Get value from string")
SECTION("UInt32_t")
{
std::string testString = "10";
- const auto testValue = utils::getValue<uint32_t>(testString);
+ const auto testValue = utils::getNumericValue<uint32_t>(testString);
REQUIRE(testValue == 10);
}
SECTION("float")
{
std::string testString = "1.f";
- const auto testValue = utils::getValue<float>(testString);
+ const auto testValue = utils::getNumericValue<float>(testString);
Approx target = Approx(1.f).margin(.01f);
REQUIRE(testValue == target);
}