M .gitmodules => .gitmodules +3 -0
@@ 113,3 113,6 @@
path = third-party/freeRTOS-kernel/freeRTOS-kernel
url = ../FreeRTOS-Kernel.git
branch = mudita
+[submodule "third-party/fakeit/FakeIt"]
+ path = third-party/fakeit/FakeIt
+ url = https://github.com/eranpeer/FakeIt.git
M module-db/Interface/CalllogRecord.cpp => module-db/Interface/CalllogRecord.cpp +19 -1
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "CalllogRecord.hpp"
@@ 29,6 29,24 @@ CalllogRecord::CalllogRecord(const CallType type, const utils::PhoneNumber::View
: presentation(PresentationType::PR_UNKNOWN), date(std::time(nullptr)), type(type), phoneNumber(number)
{}
+CalllogRecord::CalllogRecord(const CalllogRecord &rhs) : Record(rhs)
+{
+ operator=(rhs);
+}
+
+CalllogRecord &CalllogRecord::operator=(const CalllogRecord &rhs)
+{
+ ID = rhs.ID;
+ presentation = rhs.presentation;
+ date = rhs.date;
+ duration = rhs.duration;
+ type = rhs.type;
+ name = rhs.name;
+ phoneNumber = rhs.phoneNumber;
+ isRead = rhs.isRead;
+ return *this;
+}
+
std::ostream &operator<<(std::ostream &out, const CalllogRecord &rec)
{
out << " <id> " << rec.ID << " <number> " << rec.phoneNumber.getEntered() << " <e164number> "
M module-db/Interface/CalllogRecord.hpp => module-db/Interface/CalllogRecord.hpp +3 -1
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 29,8 29,10 @@ struct CalllogRecord : public Record
[[nodiscard]] std::string str() const;
CalllogRecord() = default;
+ CalllogRecord(const CalllogRecord &rhs);
CalllogRecord(const CallType type, const utils::PhoneNumber::View &number);
CalllogRecord(const CalllogTableRow &tableRow);
+ CalllogRecord &operator=(const CalllogRecord &);
};
enum class CalllogRecordField
M module-os/RTOSWrapper/include/thread.hpp => module-os/RTOSWrapper/include/thread.hpp +1 -1
@@ 53,7 53,7 @@
#ifndef CPP_FREERTOS_NO_CPP_STRINGS
#include <string>
#endif
-#include "FreeRTOS.h"
+#include <FreeRTOS.h>
#include "task.h"
#include "mutex.hpp"
#include "semaphore.hpp"
M module-services/service-cellular/CellularRequestHandler.cpp => module-services/service-cellular/CellularRequestHandler.cpp +1 -4
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "service-cellular/CellularRequestHandler.hpp"
@@ 68,12 68,9 @@ void CellularRequestHandler::handle(cellular::CallRequest &request, at::Result &
request.setHandled(false);
return;
}
- // activate call state timer
cellular.callStateTimer.start();
- // Propagate "Ringing" notification into system
cellular.bus.sendMulticast(std::make_shared<CellularRingingMessage>(request.getNumber()),
sys::BusChannel::ServiceCellularNotifications);
- AudioServiceAPI::RoutingStart(&cellular);
request.setHandled(true);
}
M module-services/service-cellular/CellularServiceAPI.cpp => module-services/service-cellular/CellularServiceAPI.cpp +2 -2
@@ 47,9 47,9 @@ bool CellularServiceAPI::HangupCall(sys::Service *serv)
return true;
}
-bool CellularServiceAPI::DismissCall(sys::Service *serv, bool addNotificationToDB)
+bool CellularServiceAPI::DismissCall(sys::Service *serv)
{
- auto msg = std::make_shared<CellularDismissCallMessage>(addNotificationToDB);
+ auto msg = std::make_shared<CellularDismissCallMessage>();
serv->bus.sendMulticast(std::move(msg), sys::BusChannel::ServiceCellularNotifications);
return true;
}
M module-services/service-cellular/CellularUrcHandler.cpp => module-services/service-cellular/CellularUrcHandler.cpp +1 -1
@@ 212,7 212,7 @@ void CellularUrcHandler::Handle(UrcResponse &urc)
for (auto &t : typesToHandle) {
if (t == urc.getURCResponseType()) {
- LOG_TRACE("call aborted");
+ LOG_INFO("call (aborted|missed) type: %s", magic_enum::enum_name(t).data());
response = std::make_unique<CellularCallAbortedNotification>();
urc.setHandled(true);
}
M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +37 -116
@@ 164,28 164,6 @@ ServiceCellular::ServiceCellular()
priv->csqHandler->handleTimerTick();
});
- callDurationTimer = sys::TimerFactory::createPeriodicTimer(
- this, "callDurationTimer", std::chrono::milliseconds{1000}, [this](sys::Timer &) {
- ongoingCall.handleCallDurationTimer();
- });
- ongoingCall.setStartCallAction([=](const CalllogRecord &rec) {
- auto call = DBServiceAPI::CalllogAdd(this, rec);
- if (call.ID == DB_ID_NONE) {
- LOG_ERROR("CalllogAdd failed");
- }
- return call;
- });
-
- ongoingCall.setEndCallAction([=](const CalllogRecord &rec) {
- if (DBServiceAPI::CalllogUpdate(this, rec) && rec.type == CallType::CT_MISSED) {
- DBServiceAPI::GetQuery(this,
- db::Interface::Name::Notifications,
- std::make_unique<db::query::notifications::Increment>(
- NotificationsRecord::Key::Calls, rec.phoneNumber));
- }
- return true;
- });
-
notificationCallback = [this](std::string &data) {
LOG_DEBUG("Notifications callback called with %u data bytes", static_cast<unsigned int>(data.size()));
@@ 222,7 200,7 @@ void ServiceCellular::SleepTimerHandler()
? currentTime - lastCommunicationTimestamp
: std::numeric_limits<TickType_t>::max() - lastCommunicationTimestamp + currentTime;
- if (!ongoingCall.isValid() && priv->state->get() >= State::ST::URCReady &&
+ if (!ongoingCall->active() && priv->state->get() >= State::ST::URCReady &&
timeOfInactivity >= constants::maxTimeWithoutCommunication.count()) {
cmux->enterSleepMode();
cpuSentinel->ReleaseMinimumFrequency();
@@ 279,7 257,16 @@ sys::ReturnCodes ServiceCellular::InitHandler()
};
cpuSentinel = std::make_shared<sys::CpuSentinel>(serviceName, this);
- ongoingCall.setCpuSentinel(cpuSentinel);
+
+ ongoingCall =
+ std::make_unique<call::Call>(this,
+ cmux.get(),
+ sys::TimerFactory::createPeriodicTimer(
+ this,
+ "callDurationTimer",
+ std::chrono::milliseconds{1000},
+ [this](sys::Timer &) { ongoingCall->handle(call::event::OngoingTimer{}); }),
+ cpuSentinel);
auto sentinelRegistrationMsg = std::make_shared<sys::SentinelRegistrationMessage>(cpuSentinel);
bus.sendUnicast(sentinelRegistrationMsg, ::service::name::system_manager);
@@ 322,9 309,10 @@ void ServiceCellular::registerMessageHandlers()
if (priv->simCard->isSimCardSelected()) {
connectionManager->onPhoneModeChange(mode);
}
- ongoingCall.setMode(mode);
+ if (ongoingCall != nullptr) {
+ ongoingCall->handle(call::event::ModeChange{mode});
+ }
});
- ongoingCall.setMode(phoneModeObserver->getCurrentPhoneMode());
phoneModeObserver->subscribe([&](sys::phone_modes::Tethering tethering) {
if (tethering == sys::phone_modes::Tethering::On) {
priv->tetheringHandler->enable();
@@ 559,9 547,6 @@ void ServiceCellular::registerMessageHandlers()
connect(typeid(CellularCallActiveNotification),
[&](sys::Message *request) -> sys::MessagePointer { return handleCallActiveNotification(request); });
- connect(typeid(CellularCallAbortedNotification),
- [&](sys::Message *request) -> sys::MessagePointer { return handleCallAbortedNotification(request); });
-
connect(typeid(CellularPowerUpProcedureCompleteNotification), [&](sys::Message *request) -> sys::MessagePointer {
return handlePowerUpProcedureCompleteNotification(request);
});
@@ 609,17 594,15 @@ void ServiceCellular::registerMessageHandlers()
connect(typeid(cellular::CallAudioEventRequest), [&](sys::Message *request) -> sys::MessagePointer {
auto message = static_cast<cellular::CallAudioEventRequest *>(request);
- ongoingCall.handleCallAudioEventRequest(message->eventType);
+ ongoingCall->handle(call::event::AudioRequest{message->eventType});
return sys::MessageNone{};
});
- connect(typeid(cellular::CallActiveNotification), [&](sys::Message *request) -> sys::MessagePointer {
- callDurationTimer.start();
- return sys::MessageNone{};
- });
-
- connect(typeid(cellular::CallEndedNotification), [&](sys::Message *request) -> sys::MessagePointer {
- callDurationTimer.stop();
+ /// aborted notification can come from:
+ /// 1. URC
+ /// 2. from here from handle cellular hangup
+ connect(typeid(CellularCallAbortedNotification), [&](sys::Message * /*request*/) -> sys::MessagePointer {
+ ongoingCall->handle(call::event::Ended{});
return sys::MessageNone{};
});
@@ 1786,22 1769,7 @@ auto ServiceCellular::handleCellularAnswerIncomingCallMessage(CellularMessage *m
-> std::shared_ptr<CellularResponseMessage>
{
LOG_INFO("%s", __PRETTY_FUNCTION__);
- if (ongoingCall.getType() != CallType::CT_INCOMING) {
- return std::make_shared<CellularResponseMessage>(true);
- }
-
- auto channel = cmux->get(CellularMux::Channel::Commands);
- auto ret = false;
- if (channel) {
- auto response = channel->cmd(at::AT::ATA);
- if (response) {
- // Propagate "CallActive" notification into system
- bus.sendMulticast(std::make_shared<CellularCallActiveNotification>(),
- sys::BusChannel::ServiceCellularNotifications);
- AudioServiceAPI::RoutingStart(this);
- ret = true;
- }
- }
+ auto ret = ongoingCall->handle(call::event::Answer{});
return std::make_shared<CellularResponseMessage>(ret);
}
@@ 1868,37 1836,17 @@ auto ServiceCellular::handleCellularCallRequestMessage(CellularCallRequestMessag
void ServiceCellular::handleCellularHangupCallMessage(CellularHangupCallMessage *msg)
{
LOG_INFO("%s", __PRETTY_FUNCTION__);
- auto channel = cmux->get(CellularMux::Channel::Commands);
- if (channel) {
- if (channel->cmd(at::AT::ATH)) {
- callStateTimer.stop();
- callEndedRecentlyTimer.start();
- if (!ongoingCall.endCall(CellularCall::Forced::True)) {
- LOG_ERROR("Failed to end ongoing call");
- }
- bus.sendMulticast(std::make_shared<CellularResponseMessage>(true, msg->type),
- sys::BusChannel::ServiceCellularNotifications);
- }
- else {
- LOG_ERROR("Call not aborted");
- bus.sendMulticast(std::make_shared<CellularResponseMessage>(false, msg->type),
- sys::BusChannel::ServiceCellularNotifications);
- }
+ if (!ongoingCall->handle(call::event::Reject{})) {
+ LOG_ERROR("Failed to end ongoing call");
}
- bus.sendMulticast(std::make_shared<CellularResponseMessage>(false, msg->type),
- sys::BusChannel::ServiceCellularNotifications);
+ callStateTimer.stop();
+ callEndedRecentlyTimer.start();
}
void ServiceCellular::handleCellularDismissCallMessage(sys::Message *msg)
{
LOG_INFO("%s", __PRETTY_FUNCTION__);
-
- auto message = static_cast<CellularDismissCallMessage *>(msg);
-
hangUpCall();
- if (message->addNotificationRequired()) {
- handleCallAbortedNotification(msg);
- }
}
auto ServiceCellular::handleDBQueryResponseMessage(db::QueryResponse *msg) -> std::shared_ptr<sys::ResponseMessage>
@@ 1932,8 1880,7 @@ auto ServiceCellular::handleCellularListCallsMessage(CellularMessage *msg) -> st
});
if (it != std::end(data)) {
- auto notification = std::make_shared<CellularCallActiveNotification>();
- bus.sendMulticast(std::move(notification), sys::BusChannel::ServiceCellularNotifications);
+ ongoingCall->handle(call::event::Answer{});
callStateTimer.stop();
return std::make_shared<CellularResponseMessage>(true);
}
@@ 1955,7 1902,8 @@ auto ServiceCellular::handleDBNotificationMessage(db::NotificationMessage *msg)
auto ServiceCellular::handleCellularRingingMessage(CellularRingingMessage *msg) -> std::shared_ptr<sys::ResponseMessage>
{
LOG_INFO("%s", __PRETTY_FUNCTION__);
- return std::make_shared<CellularResponseMessage>(ongoingCall.startOutgoing(msg->number));
+ return std::make_shared<CellularResponseMessage>(
+ ongoingCall->handle(call::event::StartCall{CallType::CT_OUTGOING, msg->number}));
}
auto ServiceCellular::handleCellularGetIMSIMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
@@ 2121,7 2069,7 @@ auto ServiceCellular::handleStateRequestMessage(sys::Message *msg) -> std::share
auto ServiceCellular::handleCallActiveNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
{
- auto ret = std::make_shared<CellularResponseMessage>(ongoingCall.setActive());
+ auto ret = std::make_shared<CellularResponseMessage>(true);
NetworkSettings networkSettings(*this);
auto currentNAT = networkSettings.getCurrentNAT();
if (currentNAT) {
@@ 2141,15 2089,7 @@ auto ServiceCellular::handleCallActiveNotification(sys::Message *msg) -> std::sh
}
return ret;
}
-auto ServiceCellular::handleCallAbortedNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
-{
- callStateTimer.stop();
- const auto ret = ongoingCall.endCall();
- if (not ret) {
- LOG_ERROR("failed to end call");
- }
- return std::make_shared<CellularResponseMessage>(ret);
-}
+
auto ServiceCellular::handlePowerUpProcedureCompleteNotification(sys::Message *msg)
-> std::shared_ptr<sys::ResponseMessage>
{
@@ 2217,31 2157,22 @@ auto ServiceCellular::handleCellularSetFlightModeMessage(sys::Message *msg) -> s
auto ServiceCellular::handleCellularRingNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
{
LOG_INFO("%s", __PRETTY_FUNCTION__);
-
- if (phoneModeObserver->isTetheringOn() || connectionManager->forceDismissCalls()) {
+ if (phoneModeObserver->isTetheringOn()) {
return std::make_shared<CellularResponseMessage>(hangUpCall());
}
- ongoingCall.handleRING();
-
+ ongoingCall->handle(call::event::RING{});
return std::make_shared<CellularResponseMessage>(true);
}
auto ServiceCellular::handleCellularCallerIdNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
{
- if (connectionManager->forceDismissCalls()) {
- return std::make_shared<CellularResponseMessage>(hangUpCall());
- }
-
auto message = static_cast<CellularCallerIdNotification *>(msg);
if (phoneModeObserver->isTetheringOn()) {
tetheringCalllog.push_back(CalllogRecord{CallType::CT_MISSED, message->getNubmer()});
return std::make_shared<CellularResponseMessage>(hangUpCallBusy());
}
- if (not ongoingCall.handleCLIP(message->getNubmer())) {
- CellularServiceAPI::DismissCall(
- this, phoneModeObserver->getCurrentPhoneMode() == sys::phone_modes::PhoneMode::DoNotDisturb);
- }
+ ongoingCall->handle(call::event::CLIP{message->getNubmer()});
return std::make_shared<CellularResponseMessage>(true);
}
@@ 2261,15 2192,11 @@ auto ServiceCellular::handleCellularSetConnectionFrequencyMessage(sys::Message *
auto ServiceCellular::hangUpCall() -> bool
{
- auto channel = cmux->get(CellularMux::Channel::Commands);
- if (channel != nullptr) {
- if (channel->cmd(at::factory(at::AT::ATH))) {
- ongoingCall.endCall();
- return true;
- }
+ auto ret = ongoingCall->handle(call::event::Ended{});
+ if (!ret) {
+ LOG_ERROR("Failed to hang up call");
}
- LOG_ERROR("Failed to hang up call");
- return false;
+ return ret;
}
auto ServiceCellular::hangUpCallBusy() -> bool
@@ 2357,12 2284,6 @@ auto ServiceCellular::logTetheringCalls() -> void
}
}
-bool ServiceCellular::areCallsFromFavouritesEnabled()
-{
- return utils::getNumericValue<bool>(
- settings->getValue(settings::Offline::callsFromFavorites, settings::SettingsScope::Global));
-}
-
TaskHandle_t ServiceCellular::getTaskHandle()
{
return xTaskGetCurrentTaskHandle();
M module-services/service-cellular/call/CMakeLists.txt => module-services/service-cellular/call/CMakeLists.txt +24 -10
@@ 1,26 1,40 @@
project(service-cellular-call)
message( "${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}" )
-add_library(${PROJECT_NAME} STATIC
+add_library(${PROJECT_NAME} STATIC
CellularCall.cpp
- CallAudio.cpp
- CallGUI.cpp
- CallDB.cpp
- CallMulticast.cpp
- CallRingGuard.cpp
+ api/CallAudio.cpp
+ api/CallGUI.cpp
+ api/CallDB.cpp
+ api/CallMulticast.cpp
+ api/CallTimer.cpp
+ api/ModemCallApi.cpp
+)
+set_source_files_properties(
+ CellularCall.cpp
+ PROPERTIES COMPILE_FLAGS
+ "-Wno-error=unused-but-set-variable"
)
target_include_directories(${PROJECT_NAME}
+ PRIVATE
PUBLIC
+ ./
./include/
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
module-sys
+ sml::utils::logger
+ PUBLIC
+ libphonenumber::libphonenumber
)
-# uncomment to add tests:
-# if (${ENABLE_TESTS})
-# add_subdirectory(tests)
-# endif ()
+if (${PROJECT_TARGET} STREQUAL "TARGET_Linux")
+ add_subdirectory(doc/uml/)
+endif()
+
+if (${ENABLE_TESTS})
+ add_subdirectory(tests)
+endif ()
D module-services/service-cellular/call/CallDB.cpp => module-services/service-cellular/call/CallDB.cpp +0 -18
@@ 1,18 0,0 @@
-// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#include "CallDB.hpp"
-#include "service-appmgr/data/CallActionsParams.hpp"
-#include <queries/notifications/QueryNotificationsIncrement.hpp>
-#include <service-appmgr/Controller.hpp>
-
-CallDB::CallDB(sys::Service &s) : owner(s)
-{}
-
-void CallDB::incrementNotAnsweredCallsNotification(const utils::PhoneNumber::View &number)
-{
- DBServiceAPI::GetQuery(
- &owner,
- db::Interface::Name::Notifications,
- std::make_unique<db::query::notifications::Increment>(NotificationsRecord::Key::Calls, number));
-}
D module-services/service-cellular/call/CallDB.hpp => module-services/service-cellular/call/CallDB.hpp +0 -21
@@ 1,21 0,0 @@
-// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#include <PhoneNumber.hpp>
-
-namespace sys
-{
- class Service;
-}
-
-class CallDB
-{
- sys::Service &owner;
-
- public:
- explicit CallDB(sys::Service &);
-
- void incrementNotAnsweredCallsNotification(const utils::PhoneNumber::View &number);
-};
D module-services/service-cellular/call/CallGUI.hpp => module-services/service-cellular/call/CallGUI.hpp +0 -28
@@ 1,28 0,0 @@
-// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#include <PhoneNumber.hpp>
-#include <Tables/CalllogTable.hpp>
-
-namespace sys
-{
- class Service;
-}
-
-class CallGUI
-{
- struct CallMeta;
- sys::Service &owner;
-
- public:
- explicit CallGUI(sys::Service &);
-
- void notifyRING();
- void notifyCLIP(const utils::PhoneNumber::View &number);
- void notifyCallStarted(utils::PhoneNumber phoneNumber, const CallType type);
- void notifyCallEnded();
- void notifyCallActive();
- void notifyCallDurationUpdate(const time_t &duration);
-};
A module-services/service-cellular/call/CallMachine.hpp => module-services/service-cellular/call/CallMachine.hpp +337 -0
@@ 0,0 1,337 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <boost/sml.hpp>
+#include <sml-utils/Logger.hpp>
+#include "call/CellularCall.hpp"
+#include "log/log.hpp"
+#include "magic_enum.hpp"
+#include <cxxabi.h>
+
+namespace call
+{
+ void setNumber(CallData &call, const utils::PhoneNumber::View &number)
+ {
+ call.record.presentation =
+ number.getFormatted().empty() ? PresentationType::PR_UNKNOWN : PresentationType::PR_ALLOWED;
+ call.record.phoneNumber = number;
+ }
+
+ const auto setIncomingCall = [](Dependencies &di, CallData &call, const utils::PhoneNumber::View &view) {
+ call.record.type = CallType::CT_INCOMING;
+ call.record.isRead = false;
+ setNumber(call, view);
+ };
+
+ const auto handleClipCommon = [](Dependencies &di, const call::event::CLIP &clip, CallData &call) {
+ const utils::PhoneNumber::View &nr = clip.number;
+ setIncomingCall(di, call, nr);
+ call.record.date = std::time(nullptr);
+
+ di.gui->notifyCLIP(nr);
+ di.multicast->notifyIdentifiedCall(nr);
+ di.db->startCall(call.record);
+ di.sentinel->HoldMinimumFrequency(bsp::CpuFrequencyMHz::Level_6);
+ };
+
+ struct RING
+ {
+ bool operator()(CallData &call)
+ {
+ return call.mode == sys::phone_modes::PhoneMode::Connected;
+ }
+ } constexpr RING;
+
+ struct HandleInit
+ {
+ void operator()(Dependencies &di, CallData &call)
+ {
+ call = CallData{};
+ call.mode = di.modem->getMode();
+ di.sentinel->ReleaseMinimumFrequency();
+ di.timer->stop();
+ }
+ } constexpr HandleInit;
+
+ struct HandleRing
+ {
+ void operator()(Dependencies &di, CallData &call)
+ {
+ auto nr = utils::PhoneNumber::View();
+ setIncomingCall(di, call, nr);
+ call.record.date = std::time(nullptr);
+
+ di.gui->notifyRING();
+ di.multicast->notifyIncommingCall();
+ di.db->startCall(call.record);
+ di.sentinel->HoldMinimumFrequency(bsp::CpuFrequencyMHz::Level_6);
+ di.audio->play();
+ }
+ } constexpr HandleRing;
+
+ struct ClipConnected
+ {
+ bool operator()(CallData &call)
+ {
+ return call.mode == sys::phone_modes::PhoneMode::Connected;
+ }
+ } constexpr ClipConnected;
+
+ struct HandleClipWithoutRing
+ {
+ void operator()(Dependencies &di, const call::event::CLIP &clip, CallData &call)
+ {
+ handleClipCommon(di, clip, call);
+ di.audio->play();
+ };
+ } constexpr HandleClipWithoutRing;
+
+ struct HandleClipConnected
+ {
+ /// does everything that clip would do + plays ring
+ void operator()(Dependencies &di, const call::event::CLIP &clip, CallData &call)
+ {
+ handleClipCommon(di, clip, call);
+ };
+ } constexpr HandleClipConnected;
+
+ struct ClipDND_OK
+ {
+ bool operator()(const call::event::CLIP &clip, Dependencies &di, CallData &call)
+ {
+ return call.mode == sys::phone_modes::PhoneMode::DoNotDisturb &&
+ di.modem->areCallsFromFavouritesEnabled() && di.db->isNumberInFavourites(clip.number);
+ }
+ } constexpr ClipDND_OK;
+
+ struct HandleClipDND
+ {
+ void operator()(const call::event::CLIP &clip, Dependencies &di, CallData &call)
+ {
+ handleClipCommon(di, clip, call);
+ di.audio->play();
+ }
+ } constexpr HandleClipDND;
+
+ struct ClipDND_NOK
+ {
+ bool operator()(Dependencies &di, CallData &call)
+ {
+ return call.mode == sys::phone_modes::PhoneMode::DoNotDisturb &&
+ not di.db->isNumberInFavourites(call.record.phoneNumber);
+ }
+ } constexpr ClipDND_NOK;
+
+ struct HandleDND_Reject
+ {
+ void operator()(Dependencies &di, const call::event::CLIP &clip, CallData &call)
+ {
+ setNumber(call, clip.number);
+ di.db->incrementNotAnsweredCallsNotification(call.record.phoneNumber);
+ di.db->endCall(call.record);
+ di.modem->rejectCall();
+ }
+ } constexpr HandleDND_Reject;
+
+ /// when we have ongoing call - handle incomming CLIP information
+ /// this happens when we answer call before first CLIP is received
+ struct HandleAddID
+ {
+ void operator()(Dependencies &di, const call::event::CLIP &clip, CallData &call)
+ {
+ const utils::PhoneNumber::View &number = clip.number;
+ setNumber(call, number);
+ di.gui->notifyCLIP(number);
+ di.multicast->notifyIdentifiedCall(number);
+ }
+ } constexpr HandleAddID;
+
+ struct HandleAnswerCall
+ {
+ void operator()(Dependencies &di, CallData &call)
+ {
+ if (not di.modem->answerIncommingCall()) {
+ throw std::runtime_error("modem failure!");
+ }
+ di.audio->routingStart();
+ di.multicast->notifyCallActive();
+ di.gui->notifyCallActive();
+ di.timer->start();
+ }
+ } constexpr HandleAnswerCall;
+
+ struct CallIncoming
+ {
+ bool operator()(CallData &call)
+ {
+ return call.record.type == CallType::CT_INCOMING;
+ }
+ } constexpr CallIncoming;
+
+ struct HandleCallTimer
+ {
+ void operator()(Dependencies &di, CallData &call)
+ {
+ di.gui->notifyCallDurationUpdate(di.timer->duration());
+ }
+ } constexpr HandleCallTimer;
+
+ struct HandleStartCall
+ {
+ void operator()(Dependencies &di, const call::event::StartCall &start, CallData &call)
+ {
+ call.record.type = start.type;
+ call.record.isRead = true;
+ call.record.phoneNumber = start.number;
+ call.record.date = std::time(nullptr);
+
+ di.db->startCall(call.record);
+ di.gui->notifyCallStarted(call.record.phoneNumber, call.record.type);
+ di.sentinel->HoldMinimumFrequency(bsp::CpuFrequencyMHz::Level_6);
+ }
+ } constexpr HandleStartCall;
+
+ struct HandleStartedCall
+ {
+ void operator()(Dependencies &di, CallData &call)
+ {
+ di.audio->routingStart();
+ di.timer->start();
+ }
+ } constexpr HandleStartedCall;
+
+ struct HandleRejectCall
+ {
+ void operator()(Dependencies &di, CallData &call)
+ {
+ di.audio->stop();
+ call.record.type = CallType::CT_REJECTED;
+ call.record.isRead = false;
+ di.db->endCall(call.record);
+ di.gui->notifyCallEnded();
+ di.modem->hangupCall();
+ }
+ } constexpr HandleRejectCall;
+
+ struct HandleMissedCall
+ {
+ void operator()(Dependencies &di, CallData &call)
+ {
+ di.audio->stop();
+ call.record.type = CallType::CT_MISSED;
+ call.record.isRead = false;
+ di.db->endCall(call.record);
+ di.gui->notifyCallEnded();
+ di.modem->hangupCall();
+ };
+ } constexpr HandleMissedCall;
+
+ struct HandleEndCall
+ {
+ void operator()(Dependencies &di, CallData &call)
+ {
+ di.audio->stop();
+ call.record.duration = di.timer->duration();
+ di.db->endCall(call.record);
+ di.gui->notifyCallEnded();
+ di.timer->stop();
+ di.modem->hangupCall();
+ }
+ } constexpr HandleEndCall;
+
+ struct HandleAudioRequest
+ {
+ void operator()(Dependencies &di, const call::event::AudioRequest &request)
+ {
+ cellular::CallAudioEventRequest::EventType event = request.event;
+ switch (event) {
+ case cellular::CallAudioEventRequest::EventType::Mute:
+ di.audio->muteCall();
+ break;
+ case cellular::CallAudioEventRequest::EventType::Unmute:
+ di.audio->unmuteCall();
+ break;
+ case cellular::CallAudioEventRequest::EventType::LoudspeakerOn:
+ di.audio->setLaudspeakerOn();
+ break;
+ case cellular::CallAudioEventRequest::EventType::LoudspeakerOff:
+ di.audio->setLaudspeakerOff();
+ break;
+ }
+ }
+ } constexpr HandleAudioRequest;
+
+ // show exception in log and notify the world about ended call
+ struct ExceptionHandler
+ {
+ void operator()(const std::runtime_error &err, Dependencies &di)
+ {
+ di.multicast->notifyCallAborted();
+ di.gui->notifyCallEnded();
+ LOG_FATAL("EXCEPTION %s", err.what());
+ }
+ } constexpr ExceptionHandler;
+
+ struct SM
+ {
+ auto operator()() const
+ {
+ namespace evt = call::event;
+ using namespace boost::sml;
+
+ return make_transition_table(
+ *"Idle"_s + on_entry<_> / HandleInit,
+
+ "Idle"_s + boost::sml::event<evt::RING>[RING] / HandleRing = "Ringing"_s,
+ "Idle"_s + boost::sml::event<evt::CLIP>[ClipConnected] / HandleClipWithoutRing = "HaveId"_s,
+ "Idle"_s + boost::sml::event<evt::CLIP>[ClipDND_OK] / HandleClipDND = "HaveId"_s,
+ "Idle"_s + boost::sml::event<evt::CLIP>[ClipDND_NOK] / HandleDND_Reject = "Idle"_s,
+ // outgoing call: Pure is Ringing (called from: handleCellularRingingMessage)
+ "Idle"_s + boost::sml::event<evt::StartCall> / HandleStartCall = "Starting"_s,
+
+ "Starting"_s + boost::sml::event<evt::Ended> / HandleEndCall = "Ongoing"_s,
+ "Starting"_s + boost::sml::event<evt::Reject> / HandleEndCall = "Ongoing"_s,
+ "Starting"_s + boost::sml::event<evt::Answer> / HandleStartedCall = "Ongoing"_s,
+
+ "Ringing"_s + boost::sml::event<evt::Answer> / HandleAnswerCall = "Ongoing"_s,
+ "Ringing"_s + boost::sml::event<evt::Reject> / HandleRejectCall = "Idle"_s,
+ "Ringing"_s + boost::sml::event<evt::Ended> / HandleRejectCall = "Idle"_s,
+ "Ringing"_s + boost::sml::event<evt::CLIP>[ClipConnected] / HandleClipConnected = "HaveId"_s,
+ "Ringing"_s + boost::sml::event<evt::CLIP>[ClipDND_OK] / HandleClipDND = "HaveId"_s,
+ "Ringing"_s + boost::sml::event<evt::CLIP>[ClipDND_NOK] / HandleDND_Reject = "Idle"_s,
+
+ "HaveId"_s + boost::sml::event<evt::Answer>[CallIncoming] / HandleAnswerCall = "Ongoing"_s,
+ "HaveId"_s + boost::sml::event<evt::Ended> / HandleMissedCall = "Idle"_s,
+ "HaveId"_s + boost::sml::event<evt::Reject> / HandleRejectCall = "Idle"_s,
+
+ "Ongoing"_s + boost::sml::event<evt::CLIP> / HandleAddID = "Ongoing"_s,
+ "Ongoing"_s + boost::sml::event<evt::OngoingTimer> / HandleCallTimer = "Ongoing"_s,
+ "Ongoing"_s + boost::sml::event<evt::AudioRequest> / HandleAudioRequest = "Ongoing"_s,
+ // end call and reject when call is ongoing is same
+ "Ongoing"_s + boost::sml::event<evt::Ended> / HandleEndCall = "Idle"_s,
+ "Ongoing"_s + boost::sml::event<evt::Reject> / HandleEndCall = "Idle"_s,
+
+ *("exceptions handling"_s) + exception<std::runtime_error> / ExceptionHandler = "Idle"_s,
+ "exceptions handling"_s + exception<std::runtime_error> / ExceptionHandler = "Idle"_s);
+ }
+ };
+
+ struct StateMachine
+ {
+ CallData call{};
+ Dependencies di{};
+ Logger logger;
+
+ explicit StateMachine(Dependencies di) : di(std::move(di))
+ {}
+
+ bool active() const
+ {
+ using namespace boost::sml;
+ return not machine.is("Idle"_s);
+ }
+
+ boost::sml::sm<SM, boost::sml::logger<Logger>> machine{logger, di, call};
+ };
+
+} // namespace call
D module-services/service-cellular/call/CallMulticast.hpp => module-services/service-cellular/call/CallMulticast.hpp +0 -22
@@ 1,22 0,0 @@
-// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#include <PhoneNumber.hpp>
-
-namespace sys
-{
- class Service;
-}
-
-class CallMulticast
-{
- sys::Service &owner;
-
- public:
- explicit CallMulticast(sys::Service &owner) : owner(owner)
- {}
- void notifyIncommingCall();
- void notifyIdentifiedCall(const utils::PhoneNumber::View &number);
-};
D module-services/service-cellular/call/CallRingGuard.cpp => module-services/service-cellular/call/CallRingGuard.cpp +0 -21
@@ 1,21 0,0 @@
-// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#include "CallRingGuard.hpp"
-#include "call/CellularCall.hpp"
-
-bool callRingGuard(CellularCall::Call &call)
-{
- return call.mode == sys::phone_modes::PhoneMode::Connected;
-}
-
-bool callClipGuard(CellularCall::Call &call)
-{
- return call.mode == sys::phone_modes::PhoneMode::DoNotDisturb && call.operations.areCallsFromFavouritesEnabled() &&
- call.operations.isNumberInFavourites();
-}
-
-bool callDNDGuard(CellularCall::Call &call)
-{
- return call.mode == sys::phone_modes::PhoneMode::DoNotDisturb;
-}
D module-services/service-cellular/call/CallRingGuard.hpp => module-services/service-cellular/call/CallRingGuard.hpp +0 -34
@@ 1,34 0,0 @@
-// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#pragma once
-
-namespace CellularCall
-{
- class Call;
-}
-
-/// Guard to check if we should ring on cellular RING URC
-/// This flow is when:
-/// - We are in normal mode
-/// - somebody calls
-/// Without this check we could never ring on private numbers
-bool callRingGuard(CellularCall::Call &call);
-
-/// Guard to check if we should ring on cellular CLIP URC
-/// This flow is when:
-/// - We are in do not disturb mode
-/// - somebody calls
-/// - we have this somebody phone number via CLIP URC
-/// It wont be ever called for private numbers
-bool callClipGuard(CellularCall::Call &call);
-
-/// Guard to check if we should place missed call notification
-/// This flow is when:
-/// - We are in do not disturb mode
-/// - somebody calls
-/// - we have this somebody phone number via CLIP/RING URC
-/// It wont be ever called for private numbers
-bool callDNDGuard(CellularCall::Call &call);
M module-services/service-cellular/call/CellularCall.cpp => module-services/service-cellular/call/CellularCall.cpp +74 -169
@@ 2,11 2,9 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "call/CellularCall.hpp"
-#include "service-cellular/call/CallRingGuard.hpp"
#include "service-cellular/ServiceCellular.hpp"
#include "service-db/agents/settings/SystemSettings.hpp"
-#include <queries/notifications/QueryNotificationsIncrement.hpp>
#include <CalllogRecord.hpp>
#include <PhoneNumber.hpp>
#include <Utils.hpp>
@@ 14,217 12,124 @@
#include <cinttypes>
#include <ctime>
+#include <memory>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
+#include <utility>
#include <vector>
+#include "CallMachine.hpp"
-namespace CellularCall
+namespace call
{
- Call::Call(ServiceCellular &owner) : owner(owner), audio(owner), multicast(owner), gui(owner), db(owner)
- {
- utils::PhoneNumber::View number = utils::PhoneNumber::View();
- const CallType type = CallType::CT_NONE;
- const time_t date = 0;
- const time_t duration = 0;
-
- clear();
-
- this->call.phoneNumber = number;
- this->call.date = date;
- this->call.duration = duration;
- this->call.type = type;
- this->call.name = number.getEntered(); // temporary set number as name
- }
- bool Call::handleRING()
- {
- if (callRingGuard(*this)) {
- startCall(utils::PhoneNumber::View(), CallType::CT_INCOMING);
- audio.play();
-
- if (!wasRinging) {
- wasRinging = true;
- gui.notifyRING();
- multicast.notifyIncommingCall();
- }
- return true;
- }
- return false;
- }
+ Call::Call()
+ {}
- bool Call::handleCLIP(const utils::PhoneNumber::View &number)
+ Call::Call(ServiceCellular *owner,
+ CellularMux *cmux,
+ sys::TimerHandle timer,
+ std::shared_ptr<sys::CpuSentinel> sentinel)
{
- setNumber(number);
- if (callClipGuard(*this)) {
- startCall(number, CallType::CT_INCOMING);
- audio.play();
- }
+ machine = std::make_unique<StateMachine>(Dependencies{std::make_unique<Audio>(owner),
+ std::make_unique<CallMulticast>(owner),
+ std::make_unique<CallGUI>(owner),
+ std::make_unique<CallDB>(owner),
+ std::make_unique<CallTimer>(std::move(timer)),
+ std::make_unique<cellular::Api>(owner, cmux),
+ std::move(sentinel)});
+ }
- if (callClipGuard(*this) || callRingGuard(*this)) {
- if (!isNumberDisplayed) {
- isNumberDisplayed = true;
- gui.notifyCLIP(number);
- multicast.notifyIdentifiedCall(number);
- }
- return true;
- }
+ Call::~Call()
+ {}
- if (callDNDGuard(*this)) {
- db.incrementNotAnsweredCallsNotification(number);
+ Call &Call::operator=(Call &&other) noexcept
+ {
+ if (not other.machine) {
+ LOG_ERROR("operator= on wrong call");
+ return *this;
}
-
- return false;
+ machine = std::make_unique<StateMachine>(std::move(other.machine->di));
+ return *this;
}
- bool Call::startOutgoing(const utils::PhoneNumber::View &number)
+ bool Call::handle(const call::event::RING &ring)
{
- return startCall(number, CallType::CT_OUTGOING);
+ if (machine == nullptr) {
+ return false;
+ };
+ return machine->machine.process_event(ring);
}
- bool Call::startCall(const utils::PhoneNumber::View &number, const CallType type)
+ bool Call::handle(const call::event::CLIP &clip)
{
- LOG_INFO("starting call");
-
- if (isValid()) {
- LOG_ERROR("call already established");
- return false;
- }
-
- if (cpuSentinel) {
- cpuSentinel->HoldMinimumFrequency(bsp::CpuFrequencyMHz::Level_6);
- }
-
- clear();
- CalllogRecord callRec{type, number};
- call = startCallAction ? startCallAction(callRec) : CalllogRecord();
- if (!call.isValid()) {
- LOG_ERROR("failed to obtain a call log record");
- clear();
- if (cpuSentinel) {
- cpuSentinel->ReleaseMinimumFrequency();
- }
- LOG_INFO("failed to start call");
+ if (machine == nullptr) {
return false;
- }
-
- gui.notifyCallStarted(number, type);
- LOG_INFO("call started");
- return true;
+ };
+ return machine->machine.process_event(clip);
}
- bool Call::setActive()
+ bool Call::handle(const call::event::StartCall &call)
{
- if (isValid()) {
- startActiveTime = utils::time::getCurrentTimestamp();
- isActiveCall = true;
- gui.notifyCallActive();
- LOG_INFO("set call active");
- return true;
- }
- LOG_ERROR("no valid call to activate");
- return false;
+ if (machine == nullptr) {
+ return false;
+ };
+ return machine->machine.process_event(call);
}
- bool Call::endCall(Forced forced)
+ bool Call::handle(const call::event::Ended &ended)
{
- audio.stop();
- LOG_INFO("ending call");
- if (!isValid()) {
- LOG_ERROR("no call to end");
+ if (machine == nullptr) {
return false;
- }
-
- if (cpuSentinel) {
- cpuSentinel->ReleaseMinimumFrequency();
- }
-
- if (isActiveCall) {
- auto endTime = utils::time::getCurrentTimestamp();
- call.duration = (endTime - startActiveTime).get();
- }
- else {
- auto callType = call.type;
- switch (callType) {
- case CallType::CT_INCOMING: {
- if (forced == Forced::True) {
- setType(CallType::CT_REJECTED);
- }
- else {
- setType(CallType::CT_MISSED);
- markUnread();
- }
- } break;
-
- case CallType::CT_OUTGOING: {
- // do nothing
- } break;
-
- default:
- LOG_ERROR("Not a valid call type %u", static_cast<int>(callType));
- return false;
- }
- }
-
- if (!(endCallAction && endCallAction(call))) {
- LOG_ERROR("CalllogUpdate failed, id %" PRIu32, call.ID);
- return false;
- }
-
- // Calllog entry was updated, ongoingCall can be cleared
- clear();
- gui.notifyCallEnded();
- LOG_INFO("call ended");
- return true;
+ };
+ return machine->machine.process_event(ended);
}
- void Call::setNumber(const utils::PhoneNumber::View &number)
+ bool Call::handle(const call::event::Reject &reject)
{
- call.presentation = number.getFormatted().empty() ? PresentationType::PR_UNKNOWN : PresentationType::PR_ALLOWED;
- call.phoneNumber = number;
+ if (machine == nullptr) {
+ return false;
+ };
+ return machine->machine.process_event(reject);
}
- void Call::setCpuSentinel(std::shared_ptr<sys::CpuSentinel> sentinel)
+ bool Call::handle(const call::event::AudioRequest &request)
{
- cpuSentinel = std::move(sentinel);
+ if (machine == nullptr) {
+ return false;
+ };
+ return machine->machine.process_event(request);
}
- void Call::handleCallAudioEventRequest(cellular::CallAudioEventRequest::EventType event)
+ bool Call::handle(const call::event::ModeChange &change)
{
- switch (event) {
- case cellular::CallAudioEventRequest::EventType::Mute:
- audio.muteCall();
- break;
- case cellular::CallAudioEventRequest::EventType::Unmute:
- audio.unmuteCall();
- break;
- case cellular::CallAudioEventRequest::EventType::LoudspeakerOn:
- audio.setLaudspeakerOn();
- break;
- case cellular::CallAudioEventRequest::EventType::LoudspeakerOff:
- audio.setLaudspeakerOff();
- break;
- }
+ if (machine == nullptr) {
+ return false;
+ };
+ machine->call.mode = change.mode;
+ return true;
}
- void Call::handleCallDurationTimer()
+
+ bool Call::handle(const call::event::Answer &answer)
{
- if (not isActiveCall) {
- return;
- }
- auto now = utils::time::getCurrentTimestamp();
- callDurationTime = (now - startActiveTime).getSeconds();
- gui.notifyCallDurationUpdate(callDurationTime);
+ if (machine == nullptr) {
+ return false;
+ };
+ return machine->machine.process_event(answer);
}
- bool Call::Operations::areCallsFromFavouritesEnabled()
+ bool Call::handle(const call::event::OngoingTimer &tim)
{
- return call.owner.areCallsFromFavouritesEnabled();
+ if (machine == nullptr) {
+ return false;
+ };
+ return machine->machine.process_event(tim);
}
- bool Call::Operations::isNumberInFavourites()
+ bool Call::active() const
{
- return DBServiceAPI::IsContactInFavourites(&call.owner, call.call.phoneNumber);
+ return machine->active();
}
} // namespace CellularCall
D module-services/service-cellular/call/README.md => module-services/service-cellular/call/README.md +0 -10
@@ 1,10 0,0 @@
-Encapsulated cellular call processing
-=====================================
-
-This library's goal is to encapsulate the whole call flow/flows we can have.
-It's end goal is to provide: actions and guards to write call state machine to asure full control over call processing.
-
-# library organisation
-
-Public api headers should be placed in `include/call/` catalog - these are exported in cmake to include paths for related libraries
-All other headers should **not** be placed there. These are private internals of the library
R module-services/service-cellular/call/CallAudio.cpp => module-services/service-cellular/call/api/CallAudio.cpp +26 -19
@@ 11,54 11,61 @@
#include <Timers/TimerFactory.hpp>
#include <gsl/util>
-struct CallRingAudio::CallMeta
+struct Audio::CallMeta
{
sys::Async<AudioStartPlaybackRequest, AudioStartPlaybackResponse> async;
};
-CallRingAudio::CallRingAudio(sys::Service &s) : owner(s), meta(new CallRingAudio::CallMeta)
+Audio::Audio(sys::Service *s) : owner(s), meta(new Audio::CallMeta)
{}
-CallRingAudio::~CallRingAudio()
+Audio::~Audio()
{
delete meta;
}
-void CallRingAudio::play()
+void Audio::play()
{
- started = true;
- const auto file = AudioServiceAPI::GetSound(&owner, audio::PlaybackType::CallRingtone);
- meta->async = owner.async_call<AudioStartPlaybackRequest, AudioStartPlaybackResponse>(
- service::name::audio, file, audio::PlaybackType::CallRingtone);
+ if (not started) {
+ started = true;
+ const auto file = AudioServiceAPI::GetSound(owner, audio::PlaybackType::CallRingtone);
+ meta->async = owner->async_call<AudioStartPlaybackRequest, AudioStartPlaybackResponse>(
+ service::name::audio, file, audio::PlaybackType::CallRingtone);
+ }
}
-void CallRingAudio::stop()
+void Audio::stop()
{
- auto _ = gsl::finally([this] { AudioServiceAPI::StopAll(&owner); });
+ auto _ = gsl::finally([this] { AudioServiceAPI::StopAll(owner); });
if (not started) {
return;
}
started = false;
- owner.sync(meta->async);
+ owner->sync(meta->async);
+}
+
+void Audio::muteCall()
+{
+ AudioServiceAPI::SendEvent(owner, audio::EventType::CallMute);
}
-void CallRingAudio::muteCall()
+void Audio::unmuteCall()
{
- AudioServiceAPI::SendEvent(&owner, audio::EventType::CallMute);
+ AudioServiceAPI::SendEvent(owner, audio::EventType::CallUnmute);
}
-void CallRingAudio::unmuteCall()
+void Audio::setLaudspeakerOn()
{
- AudioServiceAPI::SendEvent(&owner, audio::EventType::CallUnmute);
+ AudioServiceAPI::SendEvent(owner, audio::EventType::CallLoudspeakerOn);
}
-void CallRingAudio::setLaudspeakerOn()
+void Audio::setLaudspeakerOff()
{
- AudioServiceAPI::SendEvent(&owner, audio::EventType::CallLoudspeakerOn);
+ AudioServiceAPI::SendEvent(owner, audio::EventType::CallLoudspeakerOff);
}
-void CallRingAudio::setLaudspeakerOff()
+void Audio::routingStart()
{
- AudioServiceAPI::SendEvent(&owner, audio::EventType::CallLoudspeakerOff);
+ AudioServiceAPI::RoutingStart(owner);
}
R module-services/service-cellular/call/CallAudio.hpp => module-services/service-cellular/call/api/CallAudio.hpp +21 -4
@@ 8,16 8,32 @@ namespace sys
class Service;
}
-class CallRingAudio
+namespace call::api
+{
+ class Audio
+ {
+ public:
+ virtual void play() = 0;
+ virtual void stop() = 0;
+ virtual void muteCall() = 0;
+ virtual void unmuteCall() = 0;
+ virtual void setLaudspeakerOn() = 0;
+ virtual void setLaudspeakerOff() = 0;
+ virtual void routingStart() = 0;
+ virtual ~Audio() = default;
+ };
+} // namespace call::api
+
+class Audio : public call::api::Audio
{
struct CallMeta;
- sys::Service &owner;
+ sys::Service *owner;
CallMeta *meta = nullptr;
bool started = false;
public:
- explicit CallRingAudio(sys::Service &);
- ~CallRingAudio();
+ explicit Audio(sys::Service *);
+ ~Audio();
void play();
void stop();
@@ 25,4 41,5 @@ class CallRingAudio
void unmuteCall();
void setLaudspeakerOn();
void setLaudspeakerOff();
+ void routingStart();
};
A module-services/service-cellular/call/api/CallDB.cpp => module-services/service-cellular/call/api/CallDB.cpp +52 -0
@@ 0,0 1,52 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "CallDB.hpp"
+#include "service-appmgr/data/CallActionsParams.hpp"
+#include <queries/notifications/QueryNotificationsIncrement.hpp>
+#include <service-appmgr/Controller.hpp>
+#include "call/CellularCall.hpp"
+
+CallDB::CallDB(sys::Service *s) : owner(s)
+{}
+
+void CallDB::incrementNotAnsweredCallsNotification(const utils::PhoneNumber::View &number)
+{
+ DBServiceAPI::GetQuery(
+ owner,
+ db::Interface::Name::Notifications,
+ std::make_unique<db::query::notifications::Increment>(NotificationsRecord::Key::Calls, number));
+}
+
+void CallDB::startCall(CalllogRecord &rec)
+{
+ if (rec.ID == DB_ID_NONE) {
+ auto call = DBServiceAPI::CalllogAdd(owner, rec);
+ rec = call;
+ }
+ else {
+ auto ret = DBServiceAPI::CalllogUpdate(owner, rec);
+ if (not ret) {
+ throw std::runtime_error("CalllogUpdate failed");
+ }
+ }
+ if (rec.ID == DB_ID_NONE) {
+ throw std::runtime_error("CalllogAdd failed");
+ }
+}
+
+void CallDB::endCall(const CalllogRecord &rec)
+{
+ if (DBServiceAPI::CalllogUpdate(owner, rec) && rec.type == CallType::CT_MISSED) {
+ DBServiceAPI::GetQuery(
+ owner,
+ db::Interface::Name::Notifications,
+ std::make_unique<db::query::notifications::Increment>(NotificationsRecord::Key::Calls, rec.phoneNumber));
+ return;
+ }
+}
+
+bool CallDB::isNumberInFavourites(const utils::PhoneNumber::View &number)
+{
+ return DBServiceAPI::IsContactInFavourites(owner, number);
+}
A module-services/service-cellular/call/api/CallDB.hpp => module-services/service-cellular/call/api/CallDB.hpp +44 -0
@@ 0,0 1,44 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "CalllogRecord.hpp"
+#include <PhoneNumber.hpp>
+
+namespace sys
+{
+ class Service;
+}
+namespace CellularCall
+{
+ class Call;
+}
+
+namespace call::api
+{
+ class DB
+ {
+ public:
+ virtual void incrementNotAnsweredCallsNotification(const utils::PhoneNumber::View &number) = 0;
+ // overrides record data
+ virtual void startCall(CalllogRecord &rec) = 0;
+ virtual void endCall(const CalllogRecord &rec) = 0;
+ virtual bool isNumberInFavourites(const utils::PhoneNumber::View &number) = 0;
+ virtual ~DB() = default;
+ };
+
+} // namespace call::api
+
+class CallDB : public call::api::DB
+{
+ sys::Service *owner;
+
+ public:
+ explicit CallDB(sys::Service *);
+
+ void incrementNotAnsweredCallsNotification(const utils::PhoneNumber::View &number) override;
+ void startCall(CalllogRecord &rec) override;
+ void endCall(const CalllogRecord &rec) override;
+ bool isNumberInFavourites(const utils::PhoneNumber::View &number) override;
+};
R module-services/service-cellular/call/CallGUI.cpp => module-services/service-cellular/call/api/CallGUI.cpp +12 -13
@@ 6,42 6,41 @@
#include "service-appmgr/data/CallActionsParams.hpp"
#include <service-appmgr/Controller.hpp>
-CallGUI::CallGUI(sys::Service &s) : owner(s)
+CallGUI::CallGUI(sys::Service *s) : owner(s)
{}
void CallGUI::notifyRING()
{
app::manager::Controller::sendAction(
- &owner, app::manager::actions::HandleIncomingCall, std::make_unique<app::manager::actions::CallParams>());
+ owner, app::manager::actions::HandleIncomingCall, std::make_unique<app::manager::actions::CallParams>());
}
void CallGUI::notifyCLIP(const utils::PhoneNumber::View &number)
{
app::manager::Controller::sendAction(
- &owner, app::manager::actions::HandleCallerId, std::make_unique<app::manager::actions::CallParams>(number));
+ owner, app::manager::actions::HandleCallerId, std::make_unique<app::manager::actions::CallParams>(number));
}
-void CallGUI::notifyCallStarted(utils::PhoneNumber phoneNumber, const CallType type)
+void CallGUI::notifyCallStarted(const utils::PhoneNumber &number, const CallType &type)
{
- owner.bus.sendMulticast(
- std::make_shared<cellular::CallStartedNotification>(phoneNumber, type == CallType::CT_INCOMING),
- sys::BusChannel::ServiceCellularNotifications);
+ owner->bus.sendMulticast(std::make_shared<cellular::CallStartedNotification>(number, type == CallType::CT_INCOMING),
+ sys::BusChannel::ServiceCellularNotifications);
}
void CallGUI::notifyCallEnded()
{
- owner.bus.sendMulticast(std::make_shared<cellular::CallEndedNotification>(),
- sys::BusChannel::ServiceCellularNotifications);
+ owner->bus.sendMulticast(std::make_shared<cellular::CallEndedNotification>(),
+ sys::BusChannel::ServiceCellularNotifications);
}
void CallGUI::notifyCallActive()
{
- owner.bus.sendMulticast(std::make_shared<cellular::CallActiveNotification>(),
- sys::BusChannel::ServiceCellularNotifications);
+ owner->bus.sendMulticast(std::make_shared<cellular::CallActiveNotification>(),
+ sys::BusChannel::ServiceCellularNotifications);
}
void CallGUI::notifyCallDurationUpdate(const time_t &duration)
{
- owner.bus.sendMulticast(std::make_shared<cellular::CallDurationNotification>(duration),
- sys::BusChannel::ServiceCellularNotifications);
+ owner->bus.sendMulticast(std::make_shared<cellular::CallDurationNotification>(duration),
+ sys::BusChannel::ServiceCellularNotifications);
}
A module-services/service-cellular/call/api/CallGUI.hpp => module-services/service-cellular/call/api/CallGUI.hpp +43 -0
@@ 0,0 1,43 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <PhoneNumber.hpp>
+#include <Tables/CalllogTable.hpp>
+
+namespace sys
+{
+ class Service;
+}
+
+namespace call::api
+{
+ class GUI
+ {
+ public:
+ virtual void notifyRING() = 0;
+ virtual void notifyCLIP(const utils::PhoneNumber::View &number) = 0;
+ virtual void notifyCallStarted(const utils::PhoneNumber &number, const CallType &type) = 0;
+ virtual void notifyCallEnded() = 0;
+ virtual void notifyCallActive() = 0;
+ virtual void notifyCallDurationUpdate(const time_t &duration) = 0;
+ virtual ~GUI() = default;
+ };
+}; // namespace call::api
+
+class CallGUI : public call::api::GUI
+{
+ struct CallMeta;
+ sys::Service *owner;
+
+ public:
+ explicit CallGUI(sys::Service *);
+
+ void notifyRING() override;
+ void notifyCLIP(const utils::PhoneNumber::View &number) override;
+ void notifyCallStarted(const utils::PhoneNumber &number, const CallType &type) override;
+ void notifyCallEnded() override;
+ void notifyCallActive() override;
+ void notifyCallDurationUpdate(const time_t &duration) override;
+};
R module-services/service-cellular/call/CallMulticast.cpp => module-services/service-cellular/call/api/CallMulticast.cpp +17 -4
@@ 7,12 7,25 @@
void CallMulticast::notifyIncommingCall()
{
- owner.bus.sendMulticast(std::make_shared<CellularIncominCallMessage>(),
- sys::BusChannel::ServiceCellularNotifications);
+ owner->bus.sendMulticast(std::make_shared<CellularIncominCallMessage>(),
+ sys::BusChannel::ServiceCellularNotifications);
}
void CallMulticast::notifyIdentifiedCall(const utils::PhoneNumber::View &number)
{
- owner.bus.sendMulticast(std::make_shared<CellularCallerIdMessage>(number),
- sys::BusChannel::ServiceCellularNotifications);
+ owner->bus.sendMulticast(std::make_shared<CellularCallerIdMessage>(number),
+ sys::BusChannel::ServiceCellularNotifications);
+}
+
+void CallMulticast::notifyCallActive()
+{
+
+ owner->bus.sendMulticast(std::make_shared<CellularCallActiveNotification>(),
+ sys::BusChannel::ServiceCellularNotifications);
+}
+
+void CallMulticast::notifyCallAborted()
+{
+ owner->bus.sendMulticast(std::make_shared<CellularCallAbortedNotification>(),
+ sys::BusChannel::ServiceCellularNotifications);
}
A module-services/service-cellular/call/api/CallMulticast.hpp => module-services/service-cellular/call/api/CallMulticast.hpp +37 -0
@@ 0,0 1,37 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <PhoneNumber.hpp>
+
+namespace sys
+{
+ class Service;
+}
+
+namespace call::api
+{
+ class Multicast
+ {
+ public:
+ virtual void notifyIncommingCall() = 0;
+ virtual void notifyIdentifiedCall(const utils::PhoneNumber::View &number) = 0;
+ virtual void notifyCallActive() = 0;
+ virtual void notifyCallAborted() = 0;
+ virtual ~Multicast() = default;
+ };
+} // namespace call::api
+
+class CallMulticast : public call::api::Multicast
+{
+ sys::Service *owner;
+
+ public:
+ explicit CallMulticast(sys::Service *owner) : owner(owner)
+ {}
+ void notifyIncommingCall() override;
+ void notifyIdentifiedCall(const utils::PhoneNumber::View &number) override;
+ void notifyCallActive() override;
+ void notifyCallAborted() override;
+};
A module-services/service-cellular/call/api/CallTimer.cpp => module-services/service-cellular/call/api/CallTimer.cpp +24 -0
@@ 0,0 1,24 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "CallTimer.hpp"
+#include <Timers/TimerFactory.hpp>
+
+CallTimer::CallTimer(sys::TimerHandle handle) : handle(std::move(handle))
+{}
+
+void CallTimer::start()
+{
+ startActiveTime = std::time(nullptr);
+ handle.start();
+}
+
+void CallTimer::stop()
+{
+ handle.stop();
+}
+
+time_t CallTimer::duration()
+{
+ return std::time(nullptr) - startActiveTime;
+}
A module-services/service-cellular/call/api/CallTimer.hpp => module-services/service-cellular/call/api/CallTimer.hpp +31 -0
@@ 0,0 1,31 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <memory>
+#include <Timers/TimerHandle.hpp>
+
+namespace call::api
+{
+ class Timer
+ {
+ public:
+ virtual void start() = 0;
+ virtual void stop() = 0;
+ virtual time_t duration() = 0;
+ virtual ~Timer() = default;
+ };
+}; // namespace call::api
+
+class CallTimer : public call::api::Timer
+{
+ sys::TimerHandle handle;
+ std::time_t startActiveTime{};
+
+ public:
+ explicit CallTimer(sys::TimerHandle handle);
+ void start() override;
+ void stop() override;
+ time_t duration() override;
+};
A module-services/service-cellular/call/api/ModemCallApi.cpp => module-services/service-cellular/call/api/ModemCallApi.cpp +61 -0
@@ 0,0 1,61 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "ModemCallApi.hpp"
+#include <modem/mux/CellularMux.h>
+#include <stdexcept>
+#include <service-cellular/ServiceCellular.hpp>
+#include <service-db/agents/settings/SystemSettings.hpp>
+
+namespace cellular
+{
+ Api::Api(ServiceCellular *cellular, CellularMux *cmux) : cellular(cellular), cmux(cmux)
+ {}
+
+ bool Api::answerIncommingCall()
+ {
+ if (cmux == nullptr) {
+ throw std::runtime_error("call api not initialized");
+ }
+ auto channel = cmux->get(CellularMux::Channel::Commands);
+ if (channel != nullptr) {
+ auto response = channel->cmd(at::AT::ATA);
+ if (response) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool Api::hangupCall()
+ {
+ if (cmux == nullptr) {
+ throw std::runtime_error("call api not initialized");
+ }
+ auto channel = cmux->get(CellularMux::Channel::Commands);
+ return channel != nullptr && channel->cmd(at::AT::ATH);
+ }
+
+ bool Api::rejectCall()
+ {
+ return hangupCall();
+ }
+
+ bool Api::areCallsFromFavouritesEnabled()
+ {
+ if (cellular == nullptr) {
+ throw std::runtime_error("call api not initialized");
+ }
+
+ return utils::getNumericValue<bool>(
+ cellular->settings->getValue(settings::Offline::callsFromFavorites, settings::SettingsScope::Global));
+ }
+
+ sys::phone_modes::PhoneMode Api::getMode()
+ {
+ if (cellular == nullptr) {
+ throw std::runtime_error("call api not initialized");
+ }
+ return cellular->phoneModeObserver->getCurrentPhoneMode();
+ }
+} // namespace cellular
A module-services/service-cellular/call/api/ModemCallApi.hpp => module-services/service-cellular/call/api/ModemCallApi.hpp +42 -0
@@ 0,0 1,42 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "PhoneModes/PhoneMode.hpp"
+class CellularMux;
+class ServiceCellular;
+
+namespace call::api
+{
+ class Api
+ {
+ public:
+ virtual bool answerIncommingCall() = 0;
+ virtual bool hangupCall() = 0;
+ virtual bool rejectCall() = 0;
+ virtual bool areCallsFromFavouritesEnabled() = 0;
+ virtual sys::phone_modes::PhoneMode getMode() = 0;
+ virtual ~Api() = default;
+ };
+} // namespace call::api
+
+namespace cellular
+{
+ class Api : public call::api::Api
+ {
+ private:
+ ServiceCellular *cellular = nullptr;
+ CellularMux *cmux = nullptr;
+
+ public:
+ Api() = default;
+ Api(ServiceCellular *cellular, CellularMux *);
+
+ bool answerIncommingCall() override;
+ bool hangupCall() override;
+ bool rejectCall() override;
+ bool areCallsFromFavouritesEnabled() override;
+ sys::phone_modes::PhoneMode getMode() override;
+ };
+} // namespace cellular
A module-services/service-cellular/call/doc/README.md => module-services/service-cellular/call/doc/README.md +55 -0
@@ 0,0 1,55 @@
+Call documentation
+==================
+
+MuditaOS call library that is able to perform:
+- ingoring and outgoing calls
+- perform call filtering
+ - on RING (notification that there is incomming call)
+ - on CLIP (notification on identified call)
+ - based on contacts
+ - based on phone mode
+
+The call logic is one of oldest functionalities in the OS, which was being changed mulitiple times in the past.
+Therefore it has multiple visible (mis)conceptions. Curent aproach we want to keep is:
+- Call handling logic should be in CellularCall.hpp it's an interface for ServiceCellular to use to perform calls
+ - Especially all actions changing call - i.e. hanging, acting upon CLIP/RING, or setting up audio
+ - It's responsible to multicast specyfic call states to parties interested (i.e. call answered)
+- Call UI should be in application-call - and only UI, there should be no more state handling then required to show UI
+ - all actions that changes call - shoud be passed to CellularCall
+
+**NOTE:** Historically lots of state keeping was done in application call, while quite a bit of logic was glued in ServiceCellular - this is wrong way to go.
+
+
+# library organisation
+
+This library's goal is to encapsulate the whole call flow/flows we can have.
+It's end goal is to provide: actions and guards to write call state machine to asure full control over call processing.
+
+Public api headers should be placed in `include/call/` catalog - these are exported in cmake to include paths for related libraries
+All other headers should **not** be placed there. These are private internals of the library
+
+# Call - how to write logic
+
+CellularCall.hpp is written such as:
+- CellularCall is state machine processor
+- It has Call StateMachine inside and drives it
+- API it requires is in `api` catalog
+ - any api has to first define virtual interface
+ - then define platform implementation
+
+State machine is written using [boost::stm](https://boost-ext.github.io/sml/index.html)
+
+**NOTE** API catalog should and can be further split into api header and platform implementation.
+
+# Call state machine
+
+Call state machine can be generated with:
+```
+ninja call_uml
+call_uml > call.puml
+```
+
+Latest generated UML:
+
+
+**NOTE*:** The UML is generated automatically, for better readability please change lambdas to functions
A module-services/service-cellular/call/doc/call.svg => module-services/service-cellular/call/doc/call.svg +61 -0
@@ 0,0 1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="673px" preserveAspectRatio="none" style="width:1725px;height:673px;background:#FFFFFF;" version="1.1" viewBox="0 0 1725 673" width="1725px" zoomAndPan="magnify"><defs><filter height="300%" id="fagroh412bl9h" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><ellipse cx="689.42" cy="16" fill="#000000" filter="url(#fagroh412bl9h)" rx="10" ry="10" style="stroke:none;stroke-width:1.0;"/><g id="Idle"><rect fill="#FEFECE" filter="url(#fagroh412bl9h)" height="71.7561" rx="12.5" ry="12.5" style="stroke:#A80036;stroke-width:1.5;" width="108" x="635.42" y="87"/><line style="stroke:#A80036;stroke-width:1.5;" x1="635.42" x2="743.42" y1="116.0679" y2="116.0679"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="26" x="676.42" y="106.9659">Idle</text><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="55" x="640.42" y="133.896">on_entry /</text><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="85" x="643.42" y="150.2401">call::handle_init</text></g><g id="Ringing"><rect fill="#FEFECE" filter="url(#fagroh412bl9h)" height="50" rx="12.5" ry="12.5" style="stroke:#A80036;stroke-width:1.5;" width="73" x="207.92" y="276.5"/><line style="stroke:#A80036;stroke-width:1.5;" x1="207.92" x2="280.92" y1="305.5679" y2="305.5679"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="53" x="217.92" y="296.4659">Ringing</text></g><g id="HaveId"><rect fill="#FEFECE" filter="url(#fagroh412bl9h)" height="50" rx="12.5" ry="12.5" style="stroke:#A80036;stroke-width:1.5;" width="67" x="655.92" y="444"/><line style="stroke:#A80036;stroke-width:1.5;" x1="655.92" x2="722.92" y1="473.0679" y2="473.0679"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="47" x="665.92" y="463.9659">HaveId</text></g><g id="Starting"><rect fill="#FEFECE" filter="url(#fagroh412bl9h)" height="50" rx="12.5" ry="12.5" style="stroke:#A80036;stroke-width:1.5;" width="74" x="1216.42" y="444"/><line style="stroke:#A80036;stroke-width:1.5;" x1="1216.42" x2="1290.42" y1="473.0679" y2="473.0679"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="54" x="1226.42" y="463.9659">Starting</text></g><g id="Ongoing"><rect fill="#FEFECE" filter="url(#fagroh412bl9h)" height="50" rx="12.5" ry="12.5" style="stroke:#A80036;stroke-width:1.5;" width="79" x="1143.92" y="609"/><line style="stroke:#A80036;stroke-width:1.5;" x1="1143.92" x2="1222.92" y1="638.0679" y2="638.0679"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="59" x="1153.92" y="628.9659">Ongoing</text></g><!--MD5=[38f1f8b7f214ff6bc6d4b705f682283d]
+link *start to Idle--><path d="M689.42,26.32 C689.42,38.72 689.42,61.43 689.42,81.62 " fill="none" id="*start-to-Idle" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="689.42,86.69,693.4457,77.7015,689.4343,81.69,685.4457,77.6786,689.42,86.69" style="stroke:#A80036;stroke-width:1.0;"/><!--MD5=[4a14721c1eaf3d017c18f39545a6a6ef]
+link Idle to Ringing--><path d="M635.22,126.97 C510.58,134.34 212.03,155.36 182.42,189 C166.28,207.35 172.55,221.64 182.42,244 C187.26,254.96 195.39,264.71 204.2,272.89 " fill="none" id="Idle-to-Ringing" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="208.01,276.3,203.9599,267.3224,204.2801,272.9702,198.6323,273.2903,208.01,276.3" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="96" x="184.42" y="203.897">call::event::RING</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="72" x="197.92" y="221.603">[call::RING] /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="95" x="186.42" y="239.309">call::HandleRing</text><!--MD5=[b445053e7a1d5b3c2afc54b1d6f0c485]
+link Ringing to Idle--><path d="M262.94,276.26 C270.37,266.35 278.94,254.72 286.42,244 C303.15,220.04 298.04,205.09 322.42,189 C372.05,156.25 539.56,137.03 630.12,128.78 " fill="none" id="Ringing-to-Idle" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="635.18,128.33,625.8614,125.142,630.1996,128.7724,626.5692,133.1106,635.18,128.33" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="109" x="332.92" y="212.897">call::event::Reject /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="125" x="326.42" y="230.603">call::HandleRejectCall</text><!--MD5=[b445053e7a1d5b3c2afc54b1d6f0c485]
+link Ringing to Idle--><path d="M281.16,298.6 C326.33,294.62 403.48,282.27 456.42,244 C480.1,226.89 469.25,206.8 492.42,189 C532.63,158.12 588.19,141.54 630.06,132.88 " fill="none" id="Ringing-to-Idle-1" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="635.1,131.86,625.4879,129.7135,630.1982,132.8461,627.0656,137.5564,635.1,131.86" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="111" x="501.92" y="212.897">call::event::Ended /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="125" x="496.42" y="230.603">call::HandleRejectCall</text><!--MD5=[b445053e7a1d5b3c2afc54b1d6f0c485]
+link Ringing to Idle--><path d="M207.88,296.57 C150.88,289.62 45,273.04 22.42,244 C7.42,224.7 6,207.1 22.42,189 C62.87,144.43 473.7,129.44 629.74,125.34 " fill="none" id="Ringing-to-Idle-2" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="635.18,125.19,626.0799,121.4233,630.1817,125.3189,626.2861,129.4207,635.18,125.19" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="92" x="47.92" y="203.897">call::event::CLIP</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="125" x="32.92" y="221.603">[call::ClipDND_NOK] /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="138" x="26.42" y="239.309">call::HandleDND_Reject</text><!--MD5=[d9a236f899448a21faa88e2b2c7523ea]
+link Idle to HaveId--><path d="M736.63,159.18 C768.99,186.44 809.23,227.35 827.42,274 C854.18,342.59 776.89,409.65 727.39,444.11 " fill="none" id="Idle-to-HaveId" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="722.99,447.14,732.6725,445.3374,727.1102,444.3074,728.1403,438.7451,722.99,447.14" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="92" x="871.92" y="288.897">call::event::CLIP</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="128" x="855.42" y="306.603">[call::ClipConnected] /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="166" x="836.42" y="324.309">call::HandleClipWithoutRing</text><!--MD5=[d9a236f899448a21faa88e2b2c7523ea]
+link Idle to HaveId--><path d="M743.83,134.16 C853.13,157.1 1083.05,219.65 1011.42,329 C947.61,426.42 799.41,455.86 728.54,464.52 " fill="none" id="Idle-to-HaveId-1" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="723.16,465.15,732.5662,468.0695,728.1257,464.565,731.6302,460.1244,723.16,465.15" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="92" x="1039.92" y="288.897">call::event::CLIP</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="115" x="1029.92" y="306.603">[call::ClipDND_OK] /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="118" x="1028.42" y="324.309">call::HandleClipDND</text><!--MD5=[b42b0813981780530edef3df7548b0ab]
+link HaveId to Idle--><path d="M666.4,443.93 C643.12,420.43 605.39,384.58 568.42,359 C544.56,342.49 525.51,353.79 510.42,329 C497.72,308.12 495.46,293.33 510.42,274 C543.03,231.89 584.69,277.08 626.42,244 C651.94,223.77 668.28,190.35 677.91,163.91 " fill="none" id="HaveId-to-Idle" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="679.63,159.05,672.8525,166.196,677.9592,163.7626,680.3926,168.8693,679.63,159.05" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="111" x="522.92" y="297.897">call::event::Ended /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="131" x="514.42" y="315.603">call::HandleMissedCall</text><!--MD5=[b42b0813981780530edef3df7548b0ab]
+link HaveId to Idle--><path d="M689.42,443.6 C689.42,385.08 689.42,236.26 689.42,164.25 " fill="none" id="HaveId-to-Idle-1" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="689.42,159.2,685.42,168.2,689.42,164.2,693.42,168.2,689.42,159.2" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="109" x="699.92" y="297.897">call::event::Reject /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="125" x="693.42" y="315.603">call::HandleRejectCall</text><!--MD5=[4f7116d012c72546cb0d8b5f1cb34cc3]
+link Idle to Idle--><path d="M743.74,110.63 C762.89,110.43 778.42,114.56 778.42,123 C778.42,130.65 765.66,134.76 749.02,135.31 " fill="none" id="Idle-to-Idle" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="743.74,135.37,752.7851,139.2669,748.7397,135.3129,752.6937,131.2674,743.74,135.37" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="92" x="808.92" y="110.397">call::event::CLIP</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="125" x="793.92" y="128.103">[call::ClipDND_NOK] /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="138" x="787.42" y="145.809">call::HandleDND_Reject</text><!--MD5=[5038e0717032de67f19838e91765af9d]
+link Idle to Starting--><path d="M743.72,125.88 C840.16,132.19 1041.83,159.22 1155.42,274 C1202.99,322.07 1231.99,397.91 1245.1,439.08 " fill="none" id="Idle-to-Starting" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="1246.62,443.93,1247.7368,434.1447,1245.1205,439.1601,1240.105,436.5439,1246.62,443.93" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="123" x="1195.42" y="297.897">call::event::StartCall /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="118" x="1199.42" y="315.603">call::HandleStartCall</text><!--MD5=[a8a6e8911ee4b822779a5c15ea0fe9d5]
+link Starting to Ongoing--><path d="M1290.55,489.72 C1303.05,498.42 1315.56,509.91 1322.42,524 C1333.13,545.97 1336.23,558.83 1322.42,579 C1301.4,609.72 1260.13,622.97 1228,628.68 " fill="none" id="Starting-to-Ongoing" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="1222.93,629.53,1232.4686,631.9825,1227.8608,628.7011,1231.1423,624.0933,1222.93,629.53" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="111" x="1334.42" y="547.897">call::event::Ended /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="112" x="1335.42" y="565.603">call::HandleEndCall</text><!--MD5=[a8a6e8911ee4b822779a5c15ea0fe9d5]
+link Starting to Ongoing--><path d="M1216.08,473.25 C1157.2,479.22 1047.05,494.18 1023.42,524 C1008.24,543.16 1009.17,559.14 1023.42,579 C1049.34,615.09 1100.64,627.38 1138.19,631.39 " fill="none" id="Starting-to-Ongoing-1" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="1143.63,631.93,1135.0616,627.0738,1138.6537,631.4438,1134.2837,635.0359,1143.63,631.93" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="109" x="1027.42" y="547.897">call::event::Reject /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="112" x="1027.42" y="565.603">call::HandleEndCall</text><!--MD5=[a8a6e8911ee4b822779a5c15ea0fe9d5]
+link Starting to Ongoing--><path d="M1216.16,486.4 C1200.94,495.19 1184.94,507.63 1176.42,524 C1163.57,548.71 1167.77,580.95 1173.69,603.94 " fill="none" id="Starting-to-Ongoing-2" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="1175.05,609,1176.5727,599.2696,1173.7501,604.1719,1168.8478,601.3494,1175.05,609" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="117" x="1186.92" y="547.897">call::event::Answer /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="133" x="1180.42" y="565.603">call::HandleStartedCall</text><!--MD5=[2cebd8ac44a4363bc14d79ffcc983203]
+link Ringing to Ongoing--><path d="M229.14,326.58 C215.86,350.87 201.48,388.36 220.42,414 C368.8,614.81 513.34,526.44 757.42,579 C894.7,608.56 1060.6,623.86 1138.68,629.87 " fill="none" id="Ringing-to-Ongoing" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="1143.69,630.26,1135.0253,625.5777,1138.7049,629.8743,1134.4083,633.5539,1143.69,630.26" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="117" x="305.92" y="465.397">call::event::Answer /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="133" x="299.42" y="483.103">call::HandleAnswerCall</text><!--MD5=[1eb793f0abbdfe879580084477c36e66]
+link Ringing to HaveId--><path d="M235.23,326.6 C227.43,351.64 220.48,390.38 241.42,414 C268.42,444.44 546.62,461.04 650.55,466.21 " fill="none" id="Ringing-to-HaveId" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="655.66,466.46,646.8647,462.0281,650.6659,466.2174,646.4765,470.0187,655.66,466.46" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="92" x="275.42" y="373.897">call::event::CLIP</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="128" x="258.92" y="391.603">[call::ClipConnected] /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="155" x="245.42" y="409.309">call::HandleClipConnected</text><!--MD5=[1eb793f0abbdfe879580084477c36e66]
+link Ringing to HaveId--><path d="M280.94,308.31 C315.88,315.38 368.58,330.14 405.42,359 C428.43,377.01 417.36,397.44 441.42,414 C505.29,457.96 598.53,467 650.58,468.36 " fill="none" id="Ringing-to-HaveId-1" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="655.8,468.47,646.8938,464.2654,650.8013,468.3557,646.7109,472.2633,655.8,468.47" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="92" x="456.92" y="373.897">call::event::CLIP</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="115" x="446.92" y="391.603">[call::ClipDND_OK] /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="118" x="445.42" y="409.309">call::HandleClipDND</text><!--MD5=[5f70ee8c8facd2edf16dd4e5478bfbc0]
+link HaveId to Ongoing--><path d="M719.92,494.04 C753.56,519.29 810.29,558.16 865.42,579 C958.54,614.19 1075.66,626.55 1138.56,630.81 " fill="none" id="HaveId-to-Ongoing" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="1143.73,631.15,1135.0125,626.5668,1138.7408,630.8212,1134.4864,634.5495,1143.73,631.15" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="109" x="879.92" y="538.897">call::event::Answer</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="119" x="876.42" y="556.603">[call::CallIncoming] /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="133" x="869.42" y="574.309">call::HandleAnswerCall</text><!--MD5=[97d569762578c974cd2dfaf5367bfc1a]
+link Ongoing to Ongoing--><path d="M1223.28,628.98 C1241.77,628.43 1257.92,630.1 1257.92,634 C1257.92,637.53 1244.66,639.24 1228.41,639.12 " fill="none" id="Ongoing-to-Ongoing" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="1223.28,639.02,1232.2101,643.1737,1228.2793,639.1057,1232.3472,635.1749,1223.28,639.02" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="100" x="1267.92" y="630.397">call::event::CLIP /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="105" x="1266.92" y="648.103">call::HandleAddID</text><!--MD5=[97d569762578c974cd2dfaf5367bfc1a]
+link Ongoing to Ongoing--><path d="M1223.24,626.86 C1282.93,621.02 1377.92,623.4 1377.92,634 C1377.92,644.29 1288.41,646.83 1228.57,641.63 " fill="none" id="Ongoing-to-Ongoing-1" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="1223.24,641.14,1231.8359,645.9473,1228.219,641.5978,1232.5685,637.9809,1223.24,641.14" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="160" x="1383.92" y="630.397">call::event::OngoingTimer /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="123" x="1403.92" y="648.103">call::HandleCallTimer</text><!--MD5=[97d569762578c974cd2dfaf5367bfc1a]
+link Ongoing to Ongoing--><path d="M1223.2,625.93 C1322.77,613.99 1549.92,616.68 1549.92,634 C1549.92,651.03 1330.25,653.91 1228.26,642.65 " fill="none" id="Ongoing-to-Ongoing-2" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="1223.2,642.07,1231.6844,647.0716,1228.1673,642.641,1232.5979,639.1239,1223.2,642.07" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="157" x="1555.92" y="630.397">call::event::AudioRequest /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="152" x="1559.92" y="648.103">call::HandleAudioRequest</text><!--MD5=[08eaf5efa1f60c65a3812368f3ac4307]
+link Ongoing to Idle--><path d="M1222.96,631.14 C1291.38,627 1427.33,614.42 1456.42,579 C1549.86,465.28 1437.6,371.59 1327.42,274 C1240.97,197.42 890.4,147.96 748.99,130.78 " fill="none" id="Ongoing-to-Idle" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="743.64,130.13,752.0959,135.1796,748.604,130.7291,753.0544,127.2372,743.64,130.13" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="111" x="1466.42" y="382.897">call::event::Ended /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="112" x="1467.42" y="400.603">call::HandleEndCall</text><!--MD5=[08eaf5efa1f60c65a3812368f3ac4307]
+link Ongoing to Idle--><path d="M1222.92,630.44 C1297.14,624.96 1453.88,609.99 1497.42,579 C1537.16,550.72 1638.88,437.36 1588.42,359 C1489.57,205.48 1390.16,238.56 1214.42,189 C1050.08,142.65 847.54,129.34 748.96,125.53 " fill="none" id="Ongoing-to-Idle-1" style="stroke:#A80036;stroke-width:1.0;"/><polygon fill="#A80036" points="743.77,125.33,752.6153,129.6613,748.7666,125.5156,752.9123,121.6668,743.77,125.33" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="109" x="1605.42" y="382.897">call::event::Reject /</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="112" x="1605.42" y="400.603">call::HandleEndCall</text><!--MD5=[f81c42a19a41ff84caa28b19edd7dd9c]
+@startuml
+
+[*] - -> Idle
+Idle : on_entry /\n call::handle_init
+Idle - -> Ringing : call::event::RING\n [call::RING] /\n call::HandleRing
+Idle - -> HaveId : call::event::CLIP\n [call::ClipConnected] /\n call::HandleClipWithoutRing
+Idle - -> HaveId : call::event::CLIP\n [call::ClipDND_OK] /\n call::HandleClipDND
+Idle - -> Idle : call::event::CLIP\n [call::ClipDND_NOK] /\n call::HandleDND_Reject
+Idle - -> Starting : call::event::StartCall /\n call::HandleStartCall
+Starting - -> Ongoing : call::event::Ended /\n call::HandleEndCall
+Starting - -> Ongoing : call::event::Reject /\n call::HandleEndCall
+Starting - -> Ongoing : call::event::Answer /\n call::HandleStartedCall
+Ringing - -> Ongoing : call::event::Answer /\n call::HandleAnswerCall
+Ringing - -> Idle : call::event::Reject /\n call::HandleRejectCall
+Ringing - -> Idle : call::event::Ended /\n call::HandleRejectCall
+Ringing - -> HaveId : call::event::CLIP\n [call::ClipConnected] /\n call::HandleClipConnected
+Ringing - -> HaveId : call::event::CLIP\n [call::ClipDND_OK] /\n call::HandleClipDND
+Ringing - -> Idle : call::event::CLIP\n [call::ClipDND_NOK] /\n call::HandleDND_Reject
+HaveId - -> Ongoing : call::event::Answer\n [call::CallIncoming] /\n call::HandleAnswerCall
+HaveId - -> Idle : call::event::Ended /\n call::HandleMissedCall
+HaveId - -> Idle : call::event::Reject /\n call::HandleRejectCall
+Ongoing - -> Ongoing : call::event::CLIP /\n call::HandleAddID
+Ongoing - -> Ongoing : call::event::OngoingTimer /\n call::HandleCallTimer
+Ongoing - -> Ongoing : call::event::AudioRequest /\n call::HandleAudioRequest
+Ongoing - -> Idle : call::event::Ended /\n call::HandleEndCall
+Ongoing - -> Idle : call::event::Reject /\n call::HandleEndCall
+
+@enduml
+
+PlantUML version 1.2021.9(Sun Jul 25 12:13:56 CEST 2021)
+(GPL source distribution)
+Java Runtime: OpenJDK Runtime Environment
+JVM: OpenJDK 64-Bit Server VM
+Default Encoding: UTF-8
+Language: en
+Country: US
+--></g></svg><
\ No newline at end of file
A module-services/service-cellular/call/doc/uml/CMakeLists.txt => module-services/service-cellular/call/doc/uml/CMakeLists.txt +10 -0
@@ 0,0 1,10 @@
+project(call_uml)
+add_executable(${PROJECT_NAME} uml_printer.cpp)
+target_link_libraries(${PROJECT_NAME} PRIVATE
+ log
+ module-cellular
+ service-cellular-call
+ sml::sml
+ sml::utils::logger
+ test::fakeit
+)
A module-services/service-cellular/call/doc/uml/call_flow.puml => module-services/service-cellular/call/doc/uml/call_flow.puml +0 -0
A module-services/service-cellular/call/doc/uml/uml_printer.cpp => module-services/service-cellular/call/doc/uml/uml_printer.cpp +11 -0
@@ 0,0 1,11 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "call/CallEvents.hpp"
+#include <CallMachine.hpp>
+#include <sml-utils/PlantUML.hpp>
+
+int main()
+{
+ dump<call::SM>();
+}
A module-services/service-cellular/call/include/call/CallEvents.hpp => module-services/service-cellular/call/include/call/CallEvents.hpp +44 -0
@@ 0,0 1,44 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <PhoneNumber.hpp>
+#include <PhoneModes/PhoneMode.hpp>
+#include <service-cellular/CellularMessage.hpp>
+
+namespace call::event
+{
+ struct Answer
+ {};
+ struct StartCall
+ {
+ CallType type;
+ const utils::PhoneNumber::View number;
+ };
+ struct RING
+ {};
+ struct CLIP
+ {
+ const utils::PhoneNumber::View number;
+ };
+ struct AudioRequest
+ {
+ cellular::CallAudioEventRequest::EventType event;
+ };
+
+ struct ModeChange
+ {
+ sys::phone_modes::PhoneMode mode;
+ };
+
+ struct OngoingTimer
+ {};
+
+ struct Ended
+ {};
+
+ struct Reject
+ {};
+
+} // namespace call::event
M module-services/service-cellular/call/include/call/CellularCall.hpp => module-services/service-cellular/call/include/call/CellularCall.hpp +47 -109
@@ 3,11 3,12 @@
#pragma once
-#include "call/CallAudio.hpp"
-#include "call/CallGUI.hpp"
-#include "call/CallDB.hpp"
-#include "call/CallMulticast.hpp"
-#include "PhoneModes/PhoneMode.hpp"
+#include "api/CallAudio.hpp"
+#include "api/CallGUI.hpp"
+#include "api/CallDB.hpp"
+#include "api/CallTimer.hpp"
+#include "api/CallMulticast.hpp"
+#include "api/ModemCallApi.hpp"
#include <service-cellular/CellularMessage.hpp>
#include <Interface/CalllogRecord.hpp>
#include <SystemManager/CpuSentinel.hpp>
@@ 23,120 24,57 @@
#include <iosfwd>
#include <string>
#include <sys/types.h>
+#include "CallEvents.hpp"
class ServiceCellular;
-namespace CellularCall
+namespace call
{
- enum class Forced : bool
+ struct StateMachine;
+
+ struct CallData
{
- False,
- True
+ CalllogRecord record = {};
+ sys::phone_modes::PhoneMode mode = sys::phone_modes::PhoneMode::Connected;
};
- class Call
+ struct Dependencies
{
- CalllogRecord call;
- bool isActiveCall = false;
- bool wasRinging = false;
- bool isNumberDisplayed = false;
- std::function<CalllogRecord(const CalllogRecord &rec)> startCallAction;
- std::function<bool(const CalllogRecord &rec)> endCallAction;
- utils::time::Timestamp startActiveTime;
- time_t callDurationTime = 0;
-
- std::shared_ptr<sys::CpuSentinel> cpuSentinel;
-
- void setType(const CallType type)
- {
- call.type = type;
- }
-
- void markUnread()
- {
- call.isRead = false;
- }
-
- void clear()
- {
- call = CalllogRecord();
- isActiveCall = false;
- wasRinging = false;
- isNumberDisplayed = false;
- startActiveTime.set_time(0);
- callDurationTime = 0;
- }
-
- bool startCall(const utils::PhoneNumber::View &number, const CallType type);
+ std::shared_ptr<call::api::Audio> audio;
+ std::shared_ptr<call::api::Multicast> multicast;
+ std::shared_ptr<call::api::GUI> gui;
+ std::shared_ptr<call::api::DB> db;
+ std::shared_ptr<call::api::Timer> timer;
+ std::shared_ptr<call::api::Api> modem;
+ std::shared_ptr<sys::CpuSentinel> sentinel;
+ };
- ServiceCellular &owner;
- CallRingAudio audio;
- CallMulticast multicast;
- CallGUI gui;
- CallDB db;
+ class Call
+ {
+ private:
+ std::unique_ptr<StateMachine> machine;
public:
- void setMode(sys::phone_modes::PhoneMode mode)
- {
- this->mode = mode;
- }
-
- mutable sys::phone_modes::PhoneMode mode;
- explicit Call(ServiceCellular &owner);
-
- void setStartCallAction(const std::function<CalllogRecord(const CalllogRecord &rec)> callAction)
- {
- startCallAction = callAction;
- }
-
- void ongoingCallShowUI();
- void setEndCallAction(const std::function<bool(const CalllogRecord &rec)> callAction)
- {
- endCallAction = callAction;
- }
-
- bool setActive();
- void setNumber(const utils::PhoneNumber::View &number);
- bool ringAudioOnClip();
-
- bool startOutgoing(const utils::PhoneNumber::View &number);
- bool handleRING();
- bool handleCLIP(const utils::PhoneNumber::View &number);
- bool endCall(Forced forced = Forced::False);
-
- void handleCallAudioEventRequest(cellular::CallAudioEventRequest::EventType event);
-
- bool isValid() const
- {
- return call.ID != DB_ID_NONE;
- }
-
- bool isActive() const
- {
- return isActiveCall;
- }
-
- [[nodiscard]] CallType getType() const noexcept
- {
- return call.type;
- }
-
- void setCpuSentinel(std::shared_ptr<sys::CpuSentinel> sentinel);
-
- void handleCallDurationTimer();
-
- struct Operations
- {
- bool areCallsFromFavouritesEnabled();
- bool isNumberInFavourites();
-
- private:
- friend Call;
- Call &call;
- explicit Operations(Call &owner) : call(owner)
- {}
- };
- Operations operations = Operations(*this);
- friend Operations;
+ Call();
+ Call(ServiceCellular *owner,
+ CellularMux *cmux,
+ sys::TimerHandle timer,
+ std::shared_ptr<sys::CpuSentinel> sentinel);
+ ~Call();
+ Call &operator=(Call &&other) noexcept;
+
+ bool handle(const call::event::Answer &);
+ bool handle(const call::event::RING &);
+ bool handle(const call::event::StartCall &call);
+ bool handle(const call::event::CLIP &);
+ bool handle(const call::event::AudioRequest &);
+ bool handle(const call::event::ModeChange &);
+ bool handle(const call::event::OngoingTimer &);
+ bool handle(const call::event::Ended &);
+ bool handle(const call::event::Reject &);
+
+ /// return if call procesing is in any other state than `Idle`
+ /// if so - we have ongoing call in handling
+ bool active() const;
};
} // namespace CellularCall
A module-services/service-cellular/call/tests/CMakeLists.txt => module-services/service-cellular/call/tests/CMakeLists.txt +19 -0
@@ 0,0 1,19 @@
+add_catch2_executable(
+ NAME
+ call-machine
+ SRCS
+ test-CallMachine.cpp
+
+ LIBS
+ module-cellular
+ service-cellular-call
+ sml::sml
+ sml::utils::logger
+ test::fakeit
+)
+
+set_source_files_properties(
+ test-CallMachine.cpp
+ PROPERTIES COMPILE_FLAGS
+ "-Wno-error=unused-but-set-variable"
+)
A module-services/service-cellular/call/tests/test-CallMachine.cpp => module-services/service-cellular/call/tests/test-CallMachine.cpp +386 -0
@@ 0,0 1,386 @@
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "call/CallEvents.hpp"
+#include <catch2/catch.hpp>
+#include <CallMachine.hpp>
+#include <catch/fakeit.hpp>
+
+namespace mocks
+{
+ class Sentinel : public sys::CpuSentinel
+ {
+ public:
+ Sentinel() : sys::CpuSentinel{"test", nullptr, nullptr}
+ {}
+ void HoldMinimumFrequency(bsp::CpuFrequencyMHz frequencyToHold) override
+ {}
+ void ReleaseMinimumFrequency() override
+ {}
+ };
+
+ auto audio()
+ {
+ fakeit::Mock<call::api::Audio> audio;
+ fakeit::When(Method(audio, play)).AlwaysReturn();
+ fakeit::When(Method(audio, stop)).AlwaysReturn();
+ fakeit::When(Method(audio, muteCall)).AlwaysReturn();
+ fakeit::When(Method(audio, unmuteCall)).AlwaysReturn();
+ fakeit::When(Method(audio, setLaudspeakerOn)).AlwaysReturn();
+ fakeit::When(Method(audio, setLaudspeakerOff)).AlwaysReturn();
+ fakeit::When(Method(audio, routingStart)).AlwaysReturn();
+ return audio;
+ }
+
+ auto gui()
+ {
+ fakeit::Mock<call::api::GUI> gui;
+ fakeit::When(Method(gui, notifyRING)).AlwaysReturn();
+ fakeit::When(Method(gui, notifyCLIP)).AlwaysReturn();
+ fakeit::When(Method(gui, notifyCallStarted)).AlwaysReturn();
+ fakeit::When(Method(gui, notifyCallEnded)).AlwaysReturn();
+ fakeit::When(Method(gui, notifyCallActive)).AlwaysReturn();
+ fakeit::When(Method(gui, notifyCallDurationUpdate)).AlwaysReturn();
+ return gui;
+ }
+
+ auto multicast()
+ {
+ fakeit::Mock<call::api::Multicast> multicast;
+ fakeit::When(Method(multicast, notifyIncommingCall)).AlwaysReturn();
+ fakeit::When(Method(multicast, notifyIdentifiedCall)).AlwaysReturn();
+ fakeit::When(Method(multicast, notifyCallActive)).AlwaysReturn();
+ fakeit::When(Method(multicast, notifyCallAborted)).AlwaysReturn();
+ return multicast;
+ };
+
+ auto db()
+ {
+ fakeit::Mock<call::api::DB> db;
+ fakeit::When(Method(db, incrementNotAnsweredCallsNotification)).AlwaysReturn();
+ fakeit::When(Method(db, startCall)).AlwaysReturn();
+ fakeit::When(Method(db, endCall)).AlwaysReturn();
+ fakeit::When(Method(db, isNumberInFavourites)).AlwaysReturn(true);
+ return db;
+ }
+
+ auto timer()
+ {
+ fakeit::Mock<call::api::Timer> timer;
+ fakeit::When(Method(timer, start)).AlwaysReturn();
+ fakeit::When(Method(timer, stop)).AlwaysReturn();
+ fakeit::When(Method(timer, duration)).AlwaysReturn(0);
+ return timer;
+ }
+
+ auto api(bool dnd = false)
+ {
+ fakeit::Mock<call::api::Api> api;
+ fakeit::When(Method(api, answerIncommingCall)).AlwaysReturn(true);
+ fakeit::When(Method(api, hangupCall)).AlwaysReturn(true);
+ fakeit::When(Method(api, rejectCall)).AlwaysReturn(true);
+ fakeit::When(Method(api, areCallsFromFavouritesEnabled)).AlwaysReturn(true);
+ fakeit::When(Method(api, getMode))
+ .AlwaysReturn(dnd ? sys::phone_modes::PhoneMode::DoNotDisturb : sys::phone_modes::PhoneMode::Connected);
+ return api;
+ }
+
+ template <typename T> auto mock_to_shared(T *val)
+ {
+ auto t = std::shared_ptr<T>(val, [](...) {});
+ return t;
+ }
+
+ struct DIWrapper
+ {
+ decltype(mocks::audio()) audio = mocks::audio();
+ decltype(mocks::multicast()) multicast = mocks::multicast();
+ decltype(mocks::gui()) gui = mocks::gui();
+ decltype(mocks::db()) db = mocks::db();
+ decltype(mocks::timer()) timer = mocks::timer();
+ decltype(mocks::api()) api;
+ call::Dependencies di{};
+ std::shared_ptr<mocks::Sentinel> sentinel = std::make_shared<mocks::Sentinel>();
+
+ DIWrapper(bool dnd = false) : api(mocks::api(dnd))
+ {
+ auto audio_p = mock_to_shared<call::api::Audio>(&audio.get());
+ auto multicast_p = mock_to_shared<call::api::Multicast>(&multicast.get());
+ auto gui_p = mock_to_shared<call::api::GUI>(&gui.get());
+ auto db_p = mock_to_shared<call::api::DB>(&db.get());
+ auto timer_p = mock_to_shared<call::api::Timer>(&timer.get());
+ auto api_p = mock_to_shared<call::api::Api>(&api.get());
+ di = call::Dependencies{std::move(audio_p),
+ std::move(multicast_p),
+ std::move(gui_p),
+ std::move(db_p),
+ std::move(timer_p),
+ std::move(api_p),
+ sentinel};
+ }
+
+ auto get()
+ {
+ return di;
+ }
+ };
+} // namespace mocks
+
+// ----------------------------------
+// INCOMMING CALL TESTS - mode normal
+// ----------------------------------
+
+TEST_CASE("base positive call flow, answered, end from caller")
+{
+ auto number = utils::PhoneNumber("+48700800900");
+ auto di = mocks::DIWrapper();
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ // 0 rings
+ fakeit::Verify(Method(di.audio, play)).Exactly(0);
+ // 1st ring
+ REQUIRE(machine->machine.process_event(call::event::RING{}));
+ fakeit::Verify(Method(di.audio, play)).Exactly(1);
+ fakeit::Verify(Method(di.gui, notifyRING)).Exactly(1);
+ // 2nd ring - ignored
+ REQUIRE(not machine->machine.process_event(call::event::RING{}));
+ fakeit::Verify(Method(di.audio, play)).Exactly(1);
+ fakeit::Verify(Method(di.gui, notifyRING)).Exactly(1);
+
+ REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()}));
+ REQUIRE(machine->call.record.phoneNumber == number.getView());
+ fakeit::Verify(Method(di.gui, notifyCLIP)).Exactly(1);
+
+ // answer the call from the pure
+ REQUIRE(machine->machine.process_event(call::event::Answer{}));
+ fakeit::Verify(Method(di.audio, routingStart)).Exactly(1);
+ fakeit::Verify(Method(di.gui, notifyCallActive)).Exactly(1);
+
+ REQUIRE(machine->machine.process_event(call::event::OngoingTimer{}));
+ REQUIRE(machine->machine.process_event(call::event::Ended{}));
+}
+
+TEST_CASE("CLIP before ring, answered, end from caller")
+{
+ auto number = utils::PhoneNumber("+48700800900");
+ auto di = mocks::DIWrapper();
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()}));
+ REQUIRE(machine->call.record.phoneNumber == number.getView());
+ fakeit::Verify(Method(di.audio, play)).Exactly(1);
+ fakeit::Verify(Method(di.gui, notifyCLIP)).Exactly(1);
+
+ REQUIRE(machine->machine.process_event(call::event::Answer{}));
+ fakeit::Verify(Method(di.audio, routingStart)).Exactly(1);
+ fakeit::Verify(Method(di.gui, notifyCallActive)).Exactly(1);
+
+ REQUIRE(machine->machine.process_event(call::event::OngoingTimer{}));
+
+ CalllogRecord record_when_called;
+ fakeit::When(Method(di.db, endCall)).AlwaysDo([&record_when_called](const CalllogRecord &rec) {
+ record_when_called = rec;
+ });
+
+ REQUIRE(machine->machine.process_event(call::event::Ended{}));
+
+ REQUIRE(record_when_called.type == CallType::CT_INCOMING);
+ REQUIRE(record_when_called.phoneNumber == number.getView());
+}
+
+/// no clip is when: caller id is unknown
+TEST_CASE("no CLIP at all, anonymus call answered, end from caller")
+{
+ auto di = mocks::DIWrapper();
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ fakeit::Verify(Method(di.audio, play)).Exactly(0);
+
+ REQUIRE(machine->machine.process_event(call::event::RING{}));
+ fakeit::Verify(Method(di.audio, play)).Exactly(1);
+ fakeit::Verify(Method(di.gui, notifyRING)).Exactly(1);
+
+ REQUIRE(machine->machine.process_event(call::event::Answer{}));
+ fakeit::Verify(Method(di.audio, routingStart)).Exactly(1);
+ fakeit::Verify(Method(di.gui, notifyCallActive)).Exactly(1);
+
+ REQUIRE(machine->machine.process_event(call::event::OngoingTimer{}));
+
+ // we cant use fakeit matching - as it has const references, not copies of object
+ // see: https://github.com/eranpeer/FakeIt/issues/31
+ // therefore we store value to be checked and then check it
+
+ CalllogRecord record_when_called;
+ fakeit::When(Method(di.db, endCall)).AlwaysDo([&record_when_called](const CalllogRecord &rec) {
+ record_when_called = rec;
+ });
+
+ REQUIRE(machine->machine.process_event(call::event::Ended{}));
+
+ // no CLIP: there was no phone number set
+ // - therefore we call db save with empty phone nr
+ // - call type set is answered
+ REQUIRE(record_when_called.type == CallType::CT_INCOMING);
+ REQUIRE(record_when_called.phoneNumber == utils::PhoneNumber().getView());
+}
+
+TEST_CASE("reject on PURE after RING")
+{
+ auto di = mocks::DIWrapper();
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ REQUIRE(machine->machine.process_event(call::event::RING{}));
+ fakeit::Verify(Method(di.audio, play)).Exactly(1);
+
+ CalllogRecord record_when_called;
+ fakeit::When(Method(di.db, endCall)).AlwaysDo([&record_when_called](const CalllogRecord &rec) {
+ record_when_called = rec;
+ });
+
+ REQUIRE(machine->machine.process_event(call::event::Reject{}));
+
+ fakeit::Verify(Method(di.audio, stop)).Exactly(1);
+ // UI was notified to stop "calling" display
+ fakeit::Verify(Method(di.gui, notifyCallEnded)).Exactly(1);
+
+ REQUIRE(record_when_called.type == CallType::CT_REJECTED);
+}
+
+TEST_CASE("reject on PURE after CLIP")
+{
+ auto number = utils::PhoneNumber("+48700800900");
+ auto di = mocks::DIWrapper();
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ REQUIRE(machine->machine.process_event(call::event::RING{}));
+ REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()}));
+ fakeit::Verify(Method(di.audio, play)).Exactly(1);
+
+ CalllogRecord record_when_called;
+ fakeit::When(Method(di.db, endCall)).AlwaysDo([&record_when_called](const CalllogRecord &rec) {
+ record_when_called = rec;
+ });
+
+ REQUIRE(machine->machine.process_event(call::event::Reject{}));
+
+ fakeit::Verify(Method(di.audio, stop)).Exactly(1);
+ REQUIRE(record_when_called.type == CallType::CT_REJECTED);
+}
+
+TEST_CASE("call missed on PURE after CLIP")
+{
+ auto number = utils::PhoneNumber("+48700800900");
+ auto di = mocks::DIWrapper();
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ REQUIRE(machine->machine.process_event(call::event::RING{}));
+ REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()}));
+ CalllogRecord record_when_called;
+ fakeit::When(Method(di.db, endCall)).AlwaysDo([&record_when_called](const CalllogRecord &rec) {
+ record_when_called = rec;
+ });
+ REQUIRE(machine->machine.process_event(call::event::Ended{}));
+ REQUIRE(record_when_called.type == CallType::CT_MISSED);
+}
+
+// ---------------------------------
+// INCOMMING CALL TESTS - mode dnd
+// ---------------------------------
+
+TEST_CASE("call incoming - dnd - not favourite")
+{
+ auto number = utils::PhoneNumber("+48700800900");
+ auto di = mocks::DIWrapper(true);
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ fakeit::When(Method(di.db, isNumberInFavourites)).AlwaysReturn(false);
+
+ REQUIRE(not machine->machine.process_event(call::event::RING{}));
+ REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()}));
+ fakeit::Verify(Method(di.audio, play)).Exactly(0);
+ fakeit::Verify(Method(di.api, rejectCall)).Exactly(1);
+}
+
+TEST_CASE("call incoming - dnd - in favourite")
+{
+ auto number = utils::PhoneNumber("+48700800900");
+ auto di = mocks::DIWrapper(true);
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ fakeit::When(Method(di.db, isNumberInFavourites)).AlwaysReturn(true);
+
+ REQUIRE(not machine->machine.process_event(call::event::RING{}));
+ REQUIRE(machine->machine.process_event(call::event::CLIP{number.getView()}));
+ fakeit::Verify(Method(di.audio, play)).Exactly(1);
+ fakeit::Verify(Method(di.api, rejectCall)).Exactly(0);
+ using namespace boost::sml;
+ REQUIRE(machine->machine.is("HaveId"_s));
+}
+
+TEST_CASE("call incomming - proper date")
+{
+ auto di = mocks::DIWrapper();
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ REQUIRE(machine->machine.process_event(call::event::RING{}));
+
+ auto t = std::time(nullptr);
+ auto date = *std::localtime(&t);
+ auto saved = *std::localtime(&machine->call.record.date);
+
+ REQUIRE(date.tm_year == saved.tm_year);
+ REQUIRE(date.tm_mday == saved.tm_mday);
+ REQUIRE(date.tm_hour == saved.tm_hour);
+}
+
+// ---------------------------------
+// OUTGOING CALL TESTS - mode normal
+// ---------------------------------
+
+TEST_CASE("call outgoing - answered")
+{
+ auto number = utils::PhoneNumber("+48700800900");
+ auto di = mocks::DIWrapper();
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ REQUIRE(machine->machine.process_event(call::event::StartCall{CallType::CT_OUTGOING, number.getView()}));
+ REQUIRE(machine->machine.process_event(call::event::Answer{}));
+ fakeit::Verify(Method(di.audio, play)).Exactly(0);
+ REQUIRE(machine->machine.process_event(call::event::Ended{}));
+ fakeit::Verify(Method(di.audio, stop)).Exactly(1);
+}
+
+TEST_CASE("call outgoing - ended, before answered")
+{
+ auto number = utils::PhoneNumber("+48700800900");
+ auto di = mocks::DIWrapper();
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ REQUIRE(machine->machine.process_event(call::event::StartCall{CallType::CT_OUTGOING, number.getView()}));
+ fakeit::Verify(Method(di.audio, play)).Exactly(0);
+ REQUIRE(machine->machine.process_event(call::event::Ended{}));
+ fakeit::Verify(Method(di.audio, stop)).Exactly(1);
+}
+
+// ---------------------------------
+// TEST EXCEPTION ABORTION HANDLER
+// ---------------------------------
+
+TEST_CASE("test call exception abort")
+{
+ auto number = utils::PhoneNumber("+48700800900");
+ auto di = mocks::DIWrapper();
+ auto machine = std::make_unique<call::StateMachine>(di.get());
+
+ REQUIRE(machine->machine.process_event(call::event::StartCall{CallType::CT_OUTGOING, number.getView()}));
+ REQUIRE(machine->machine.process_event(call::event::Answer{}));
+
+ fakeit::When(Method(di.audio, stop)).AlwaysDo([] {
+ throw std::runtime_error("lol");
+ return true;
+ });
+
+ REQUIRE(machine->machine.process_event(call::event::Ended{}));
+
+ fakeit::Verify(Method(di.multicast, notifyCallAborted)).Exactly(1);
+}
M module-services/service-cellular/connection-manager/ConnectionManager.cpp => module-services/service-cellular/connection-manager/ConnectionManager.cpp +1 -7
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include <module-services/service-cellular/service-cellular/connection-manager/ConnectionManager.hpp>
@@ 10,10 10,8 @@ auto ConnectionManager::onPhoneModeChange(sys::phone_modes::PhoneMode mode) -> b
cellular->holdMinimumCpuFrequency();
if (mode == sys::phone_modes::PhoneMode::Offline) {
- forceDismissCallsFlag = true;
return handleModeChangeToCommonOffline();
}
- forceDismissCallsFlag = false;
return handleModeChangeToConnected();
}
@@ 129,10 127,6 @@ auto ConnectionManager::handleModeChangeToConnected() -> bool
return true;
}
-auto ConnectionManager::forceDismissCalls() -> bool
-{
- return forceDismissCallsFlag;
-}
void ConnectionManager::retryOnFail()
{
if (failRetries < maxFailRetries) {
M module-services/service-cellular/connection-manager/ConnectionManagerCellularCommands.cpp => module-services/service-cellular/connection-manager/ConnectionManagerCellularCommands.cpp +3 -16
@@ 64,23 64,10 @@ auto ConnectionManagerCellularCommands::clearNetworkIndicator() -> bool
return true;
}
-auto ConnectionManagerCellularCommands::hangUpOngoingCall() -> bool
+void ConnectionManagerCellularCommands::hangUpOngoingCall()
{
- if (cellular.ongoingCall.isActive()) {
- auto channel = cellular.cmux->get(CellularMux::Channel::Commands);
- if (channel) {
- if (channel->cmd(at::factory(at::AT::ATH))) {
- cellular.callStateTimer.stop();
- if (!cellular.ongoingCall.endCall(CellularCall::Forced::True)) {
- LOG_ERROR("Failed to end ongoing call");
- return false;
- }
- auto msg = std::make_shared<CellularCallAbortedNotification>();
- cellular.bus.sendMulticast(msg, sys::BusChannel::ServiceCellularNotifications);
- }
- }
- }
- return true;
+ CellularHangupCallMessage msg;
+ cellular.handleCellularHangupCallMessage(&msg);
}
auto ConnectionManagerCellularCommands::isConnectionTimerActive() -> bool
M module-services/service-cellular/service-cellular/CellularMessage.hpp => module-services/service-cellular/service-cellular/CellularMessage.hpp +1 -10
@@ 613,17 613,8 @@ class CellularHangupCallMessage : public CellularMessage
class CellularDismissCallMessage : public CellularMessage
{
public:
- CellularDismissCallMessage(bool addNotificationToDB)
- : CellularMessage(Type::DismissCall), addNotificationToDB{addNotificationToDB}
+ CellularDismissCallMessage() : CellularMessage(Type::DismissCall)
{}
-
- auto addNotificationRequired() const noexcept -> bool
- {
- return addNotificationToDB;
- }
-
- private:
- const bool addNotificationToDB;
};
class CellularListCallsMessage : public CellularMessage
M module-services/service-cellular/service-cellular/CellularServiceAPI.hpp => module-services/service-cellular/service-cellular/CellularServiceAPI.hpp +1 -1
@@ 28,7 28,7 @@ namespace CellularServiceAPI
bool AnswerIncomingCall(sys::Service *serv);
bool HangupCall(sys::Service *serv);
- bool DismissCall(sys::Service *serv, bool addNotificationToDB);
+ bool DismissCall(sys::Service *serv);
/*
* @brief Its calls sercive-cellular for selected SIM IMSI number.
* @param serv pointer to caller service.
M module-services/service-cellular/service-cellular/ServiceCellular.hpp => module-services/service-cellular/service-cellular/ServiceCellular.hpp +4 -3
@@ 110,7 110,6 @@ class ServiceCellular : public sys::Service
sys::TimerHandle ussdTimer;
sys::TimerHandle simTimer;
sys::TimerHandle csqTimer;
- sys::TimerHandle callDurationTimer;
// used to enter modem sleep mode
sys::TimerHandle sleepTimer;
@@ 136,7 135,7 @@ class ServiceCellular : public sys::Service
std::vector<std::string> messageParts;
- CellularCall::Call ongoingCall = CellularCall::Call(*this);
+ std::unique_ptr<call::Call> ongoingCall;
std::vector<CalllogRecord> tetheringCalllog;
ussd::State ussdState = ussd::State::none;
@@ 256,6 255,7 @@ class ServiceCellular : public sys::Service
friend class packet_data::PDPContext;
friend class packet_data::PacketData;
friend class ConnectionManagerCellularCommands;
+ friend class cellular::Api;
void volteChanged(const std::string &value);
void apnListChanged(const std::string &value);
@@ 266,8 266,10 @@ class ServiceCellular : public sys::Service
void handleCellularHangupCallMessage(CellularHangupCallMessage *msg);
void handleCellularDismissCallMessage(sys::Message *msg);
auto handleDBQueryResponseMessage(db::QueryResponse *msg) -> std::shared_ptr<sys::ResponseMessage>;
+ /// when we start call from Pure -> to the calee, then we poll for the moment that the calee answered the call
auto handleCellularListCallsMessage(CellularMessage *msg) -> std::shared_ptr<sys::ResponseMessage>;
auto handleDBNotificationMessage(db::NotificationMessage *msg) -> std::shared_ptr<sys::ResponseMessage>;
+ /// handle mudita phone -> world ringing (not AT RING!)
auto handleCellularRingingMessage(CellularRingingMessage *msg) -> std::shared_ptr<sys::ResponseMessage>;
auto handleCellularCallerIdMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
auto handleCellularGetIMSIMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
@@ 288,7 290,6 @@ class ServiceCellular : public sys::Service
auto handleStateRequestMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
auto handleCallActiveNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
- auto handleCallAbortedNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
auto handlePowerUpProcedureCompleteNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
auto handlePowerDownDeregisteringNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
auto handlePowerDownDeregisteredNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
M module-services/service-cellular/service-cellular/connection-manager/ConnectionManager.hpp => module-services/service-cellular/service-cellular/connection-manager/ConnectionManager.hpp +1 -11
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 51,11 51,6 @@ class ConnectionManager
*/
void onTimerTick();
- /// Should we always dismiss incoming calls?
- /// @return true or false depending on state of forceDismissCallsMode flag
- /// @see forceDismissCallsMode
- auto forceDismissCalls() -> bool;
-
private:
bool isFlightMode;
std::chrono::minutes connectionInterval{0};
@@ 65,11 60,6 @@ class ConnectionManager
bool onlinePeriod = false;
int failRetries = 0;
- /// Flag determining if we should always dismiss incoming calls - even when
- /// we are in offline mode (messages only) and we connect to network to poll
- /// for new messages
- bool forceDismissCallsFlag = false;
-
/**
* @brief Checks if flightMode and connection interval are set as Messages only mode
* @return true when Messages only is configured, false when not
M module-services/service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommands.hpp => module-services/service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommands.hpp +2 -2
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 16,7 16,7 @@ class ConnectionManagerCellularCommands : public ConnectionManagerCellularComman
auto connectToNetwork() -> bool final;
auto isConnectedToNetwork() -> std::optional<bool> final;
auto clearNetworkIndicator() -> bool final;
- auto hangUpOngoingCall() -> bool final;
+ auto hangUpOngoingCall() -> void final;
auto isConnectionTimerActive() -> bool final;
void startConnectionTimer() final;
void stopConnectionTimer() final;
M module-services/service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommandsInterface.hpp => module-services/service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommandsInterface.hpp +2 -3
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 34,9 34,8 @@ class ConnectionManagerCellularCommandsInterface
virtual auto clearNetworkIndicator() -> bool = 0;
/**
* @brief Checks if there is ongoing call and terminates it
- * @return true on success, false on fail
*/
- virtual auto hangUpOngoingCall() -> bool = 0;
+ virtual void hangUpOngoingCall() = 0;
/**
* @brief Checks if connection Timer is active
* @return true when timer is active, false when not
M module-services/service-db/DBServiceAPI.cpp => module-services/service-db/DBServiceAPI.cpp +2 -8
@@ 244,10 244,7 @@ auto DBServiceAPI::CalllogRemove(sys::Service *serv, uint32_t id) -> bool
LOG_ERROR("DB response error, return code: %s", c_str(ret.first));
return false;
}
- if ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode != 0)) {
- return true;
- }
- return false;
+ return ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode != 0));
}
auto DBServiceAPI::CalllogUpdate(sys::Service *serv, const CalllogRecord &rec) -> bool
@@ 260,10 257,7 @@ auto DBServiceAPI::CalllogUpdate(sys::Service *serv, const CalllogRecord &rec) -
LOG_ERROR("DB response error, return code: %s", c_str(ret.first));
return false;
}
- if ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode != 0)) {
- return true;
- }
- return false;
+ return ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode != 0));
}
auto DBServiceAPI::DBBackup(sys::Service *serv, std::string backupPath) -> bool
M module-services/service-evtmgr/battery/BatteryController.cpp => module-services/service-evtmgr/battery/BatteryController.cpp +5 -4
@@ 113,10 113,11 @@ void sevm::battery::BatteryController::poll()
}
void sevm::battery::BatteryController::printCurrentState()
{
- LOG_INFO("Charger state:%s", magic_enum::enum_name(Store::Battery::get().state).data());
- LOG_INFO("Battery SOC:%d", Store::Battery::get().level);
- LOG_INFO("Battery voltage:%" PRIu32 "mV", charger->getBatteryVoltage());
- LOG_INFO("Battery state:%s", magic_enum::enum_name(Store::Battery::get().levelState).data());
+ LOG_INFO("Charger state:%s Battery SOC %d voltage: %" PRIu32 "mV state: %s",
+ magic_enum::enum_name(Store::Battery::get().state).data(),
+ Store::Battery::get().level,
+ charger->getBatteryVoltage(),
+ magic_enum::enum_name(Store::Battery::get().levelState).data());
}
void sevm::battery::BatteryController::update()
{
M module-sys/SystemManager/include/SystemManager/CpuSentinel.hpp => module-sys/SystemManager/include/SystemManager/CpuSentinel.hpp +3 -2
@@ 26,8 26,8 @@ namespace sys
std::function<void(bsp::CpuFrequencyMHz)> callback = nullptr);
[[nodiscard]] auto GetName() const noexcept -> std::string;
- void HoldMinimumFrequency(bsp::CpuFrequencyMHz frequencyToHold);
- void ReleaseMinimumFrequency();
+ virtual void HoldMinimumFrequency(bsp::CpuFrequencyMHz frequencyToHold);
+ virtual void ReleaseMinimumFrequency();
[[nodiscard]] auto GetFrequency() const noexcept -> bsp::CpuFrequencyMHz;
@@ 36,6 36,7 @@ namespace sys
void ReadRegistrationData(bsp::CpuFrequencyMHz frequencyHz);
TaskHandle_t getTask();
std::string getReason();
+ virtual ~CpuSentinel() = default;
protected:
const std::string name;
M module-utils/log/Logger.cpp => module-utils/log/Logger.cpp +1 -1
@@ 26,8 26,8 @@ namespace Log
{
filtered = {
{"ApplicationManager", logger_level::LOGINFO},
-#if (!LOG_SENSITIVE_DATA_ENABLED)
{"CellularMux", logger_level::LOGINFO},
+#if (!LOG_SENSITIVE_DATA_ENABLED)
{"ServiceCellular", logger_level::LOGINFO},
#endif
{"ServiceAntenna", logger_level::LOGERROR},
M module-utils/unicode/utf8/CMakeLists.txt => module-utils/unicode/utf8/CMakeLists.txt +1 -1
@@ 10,7 10,7 @@ target_include_directories(utf8
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
-target_link_libraries(utf8 PRIVATE log-api)
+target_link_libraries(utf8 PRIVATE log-api log)
if (${ENABLE_TESTS})
add_subdirectory(test)
M products/PurePhone/services/db/ServiceDB.cpp => products/PurePhone/services/db/ServiceDB.cpp +1 -0
@@ 172,6 172,7 @@ sys::MessagePointer ServiceDB::DataReceivedHandler(sys::DataMessage *msgl, sys::
auto record = std::make_unique<std::vector<CalllogRecord>>();
msg->record.ID = DB_ID_NONE;
auto ret = calllogRecordInterface->Add(msg->record);
+ LOG_INFO("HERE! ADD! SUCCESS? %s", ret ? "SUCCESS" : "FAIL");
if (ret) {
// return the newly added record
msg->record = calllogRecordInterface->GetByID(calllogRecordInterface->GetLastID());
M third-party/CMakeLists.txt => third-party/CMakeLists.txt +1 -0
@@ 4,6 4,7 @@
add_subdirectory(base64)
add_subdirectory(date)
add_subdirectory(reedgefs)
+add_subdirectory(fakeit)
add_subdirectory(fatfs)
add_subdirectory(gsl)
add_subdirectory(hash-library)
A third-party/fakeit/CMakeLists.txt => third-party/fakeit/CMakeLists.txt +4 -0
@@ 0,0 1,4 @@
+add_library(fakeit INTERFACE)
+add_library(test::fakeit ALIAS fakeit)
+
+target_include_directories(fakeit INTERFACE FakeIt/single_header/)
A third-party/fakeit/FakeIt => third-party/fakeit/FakeIt +1 -0
@@ 0,0 1,1 @@
+Subproject commit 98979d2784d270514c3d4cde18d4370d1381d866
M third-party/sml-utils/CMakeLists.txt => third-party/sml-utils/CMakeLists.txt +1 -1
@@ 1,4 1,4 @@
add_library(sml-logger INTERFACE)
add_library(sml::utils::logger ALIAS sml-logger)
-target_include_directories(sml-logger INTERFACE include/)
+target_include_directories(sml-logger INTERFACE include/)
M third-party/sml-utils/include/sml-utils/Logger.hpp => third-party/sml-utils/include/sml-utils/Logger.hpp +4 -4
@@ 11,12 11,12 @@ struct Logger
template <class SM, class TEvent> void log_process_event(const TEvent &)
{
LOG_DEBUG(
- "[%s][process_event] %s\n", boost::sml::aux::get_type_name<SM>(), boost::sml::aux::get_type_name<TEvent>());
+ "[%s][process_event] %s", boost::sml::aux::get_type_name<SM>(), boost::sml::aux::get_type_name<TEvent>());
}
template <class SM, class TGuard, class TEvent> void log_guard(const TGuard &, const TEvent &, bool result)
{
- LOG_DEBUG("[%s][guard] %s %s %s\n",
+ LOG_DEBUG("[%s][guard] %s %s %s",
boost::sml::aux::get_type_name<SM>(),
boost::sml::aux::get_type_name<TGuard>(),
boost::sml::aux::get_type_name<TEvent>(),
@@ 25,7 25,7 @@ struct Logger
template <class SM, class TAction, class TEvent> void log_action(const TAction &, const TEvent &)
{
- LOG_DEBUG("[%s][action] %s %s\n",
+ LOG_DEBUG("[%s][action] %s %s",
boost::sml::aux::get_type_name<SM>(),
boost::sml::aux::get_type_name<TAction>(),
boost::sml::aux::get_type_name<TEvent>());
@@ 34,6 34,6 @@ struct Logger
template <class SM, class TSrcState, class TDstState>
void log_state_change(const TSrcState &src, const TDstState &dst)
{
- LOG_DEBUG("[%s][transition] %s -> %s\n", boost::sml::aux::get_type_name<SM>(), src.c_str(), dst.c_str());
+ LOG_DEBUG("[%s][transition] %s -> %s", boost::sml::aux::get_type_name<SM>(), src.c_str(), dst.c_str());
}
};
A third-party/sml-utils/include/sml-utils/PlantUML.hpp => third-party/sml-utils/include/sml-utils/PlantUML.hpp +377 -0
@@ 0,0 1,377 @@
+// Adapted example from:
+//
+// Copyright (c) 2016-2020 Kris Jusiak (kris at jusiak dot net)
+//
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <algorithm>
+#include <boost/sml.hpp>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+namespace sml = boost::sml;
+
+inline void do_indent(unsigned int indent) { std::cout << std::string(indent, ' '); }
+
+// use this to track initialization for orthogonal states
+bool state_initialized = false; // NOLINT(misc-definitions-in-headers)
+// use this to track which submachines have already been dumped so they don't get dumped twice
+std::vector<std::string> completed_submachines; // NOLINT(misc-definitions-in-headers)
+
+/** allows for checking if the type is sml::front::seq_
+ * This type is used by sml when there are lists of actions.
+ */
+template <class... Ts>
+struct is_seq_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming)
+template <class... Ts>
+struct is_seq_<sml::front::seq_<Ts...>> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming)
+
+/** allows for checking if the type is sml::front::not_
+ * This type is used by sml inside of guards, when the guard value is negated with !
+ *
+ * The partial specialization matches if the type passed in is sml::front::not_, causing the struct to
+ * inherit from sml::aux::true_type, which gives it a member called "value" that is set to true.
+ * If the type passed doesn't match sml::front::not_, it'll match the generic is_not_ which inherits
+ * from sml::aux::false_type, giving it a member called "value" that is set to false.
+ */
+template <class... Ts>
+struct is_not_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming)
+template <class... Ts>
+struct is_not_<sml::front::not_<Ts...>> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming)
+
+/** provides access to the template parameter type of an sml::front::not_<T>
+ */
+template <class T>
+struct strip_not_ {
+ using type = T;
+}; // NOLINT(readability-identifier-naming)
+template <class T>
+struct strip_not_<sml::front::not_<T>> {
+ using type = T;
+}; // NOLINT(readability-identifier-naming)
+
+/** allows for checking if the type is sml::front::and_
+ * This type is used by sml inside of guards when two guard functions are combined with &&
+ */
+template <class... Ts>
+struct is_and_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming)
+template <class... Ts>
+struct is_and_<sml::front::and_<Ts...>> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming)
+
+/** allows for checking if the type is sml::front::or_
+ * This type is used by sml inside of guards when two guard functions are combined with ||
+ */
+template <class... Ts>
+struct is_or_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming)
+template <class... Ts>
+struct is_or_<sml::front::or_<Ts...>> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming)
+
+/** uses std::tuple_element and std::tuple to access the Nth type in a parameter pack
+ */
+template <int N, class... Ts>
+using NthTypeOf = typename std::tuple_element<N, std::tuple<Ts...>>::type;
+
+/** gets the size of a parameter pack
+ * this isn't really necessary, sizeof...(Ts) can be used directly instead
+ */
+template <class... Ts>
+struct count { // NOLINT(readability-identifier-naming)
+ static const std::size_t value = sizeof...(Ts); // NOLINT(readability-identifier-naming)
+};
+
+/** allows for checking if the type is sml::aux::zero_wrapper
+ * sml puts this around types inside of guards and event sequences
+ */
+template <class T>
+struct is_zero_wrapper : sml::aux::false_type {}; // NOLINT(readability-identifier-naming)
+template <class T>
+struct is_zero_wrapper<sml::aux::zero_wrapper<T>> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming)
+
+/** if T is a zero wrapper, ::type will be the inner type. if not, it will be T.
+ */
+template <class T>
+struct strip_zero_wrapper {
+ using type = T;
+}; // NOLINT(readability-identifier-naming)
+template <class T>
+struct strip_zero_wrapper<sml::aux::zero_wrapper<T>> {
+ using type = T;
+}; // NOLINT(readability-identifier-naming)
+
+/** accesses the type of a state-machine, sml::back::sm
+ */
+template <class T>
+struct submachine_type {
+ using type = T;
+}; // NOLINT(readability-identifier-naming)
+template <class T>
+struct submachine_type<sml::back::sm<T>> {
+ using type = typename T::sm;
+}; // NOLINT(readability-identifier-naming)
+
+/** print the types inside a sml::front::seq_
+ * These types came from a list of actions.
+ */
+template <class... Ts>
+struct print_seq_types { // NOLINT(readability-identifier-naming)
+ template <int I>
+ static void func() {
+ constexpr auto param_pack_empty = (sizeof...(Ts) == I);
+ if constexpr (!param_pack_empty) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
+ using current_type = NthTypeOf<I, Ts...>;
+ if constexpr (is_seq_<typename current_type::type>::value) { // NOLINT(readability-braces-around-statements)
+ // handle nested seq_ types, these happen when there are 3 or more actions
+ print_seq_types<typename current_type::type>::template func<0>();
+ } else { // NOLINT(readability-misleading-indentation)
+ // print this param directly
+ std::cout << sml::aux::string<typename strip_zero_wrapper<current_type>::type>{}.c_str();
+ }
+ if constexpr (I + 1 < sizeof...(Ts)) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
+ std::cout << ",\\n ";
+ }
+ print_seq_types<Ts...>::template func<I + 1>();
+ }
+ }
+};
+template <class... Ts>
+struct print_seq_types<sml::front::seq_<Ts...>> { // NOLINT(readability-identifier-naming)
+ template <int I>
+ static void func() {
+ print_seq_types<Ts...>::template func<0>();
+ }
+};
+
+/** print the types inside a guard
+ * These can be a functor, an sml::front::not_, an sml::front::and_, or an sml::front::or_ which makes
+ * this one more complicated. They also involve the zero_wrapper.
+ * The various partial specializations handle all of the possible types.
+ */
+template <class... Ts>
+struct print_guard { // NOLINT(readability-identifier-naming)
+ template <int I>
+ static void func(const std::string& sep = "") {
+ constexpr auto param_pack_empty = (sizeof...(Ts) == I);
+ if constexpr (!param_pack_empty) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
+ using current_type = NthTypeOf<I, Ts...>;
+ if constexpr (is_zero_wrapper<
+ current_type>::value) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
+ // unwrap the zero_wrapper and put it back into the recursion, it could be anything
+ print_guard<typename strip_zero_wrapper<current_type>::type>::template func<0>();
+ } else { // NOLINT(readability-misleading-indentation)
+ // it's just a functor, print it
+ std::cout << sml::aux::string<current_type>{}.c_str();
+ }
+
+ // if we're not at the end, print the separator
+ if constexpr (I + 1 < sizeof...(Ts)) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
+ if (!sep.empty()) {
+ std::cout << sep;
+ }
+ }
+
+ // keep the recursion going, call for the next type in the parameter pack
+ print_guard<Ts...>::template func<I + 1>(sep);
+ }
+ }
+};
+template <class T>
+struct print_guard<sml::front::not_<T>> { // NOLINT(readability-identifier-naming)
+ template <int I>
+ static void func(const std::string& /*sep*/ = "") {
+ std::cout << "!" << sml::aux::string<typename strip_zero_wrapper<T>::type>{}.c_str();
+ }
+};
+template <class... Ts>
+struct print_guard<sml::front::and_<Ts...>> { // NOLINT(readability-identifier-naming)
+ template <int I>
+ static void func(const std::string& /*sep*/ = "") {
+ constexpr auto param_pack_empty = (sizeof...(Ts) == I);
+ if constexpr (!param_pack_empty) {
+ print_guard<Ts...>::template func<I>(" &&\\n ");
+ }
+ }
+};
+template <class... Ts>
+struct print_guard<sml::front::or_<Ts...>> { // NOLINT(readability-identifier-naming)
+ template <int I>
+ static void func(const std::string& /*sep*/ = "") {
+ constexpr auto param_pack_empty = (sizeof...(Ts) == I);
+ if constexpr (!param_pack_empty) {
+ print_guard<Ts...>::template func<I>(" ||\\n ");
+ }
+ }
+};
+
+// forward declaration of dump_transitions
+template <typename...>
+struct dump_transitions;
+
+template <int N, class T>
+void dump_transition() noexcept {
+ constexpr auto src_is_sub_sm =
+ !sml::aux::is_same<sml::aux::type_list<>, sml::back::get_sub_sms<typename T::src_state>>::value;
+ constexpr auto dst_is_sub_sm =
+ !sml::aux::is_same<sml::aux::type_list<>, sml::back::get_sub_sms<typename T::dst_state>>::value;
+
+ std::string src_state, dst_state;
+ // NOLINTNEXTLINE(readability-braces-around-statements)
+ if constexpr (src_is_sub_sm) {
+ src_state = std::string{sml::aux::string<typename submachine_type<typename T::src_state>::type>{}.c_str()};
+ } else { // NOLINT(readability-misleading-indentation)
+ src_state = std::string{sml::aux::string<typename T::src_state>{}.c_str()};
+ }
+
+ // NOLINTNEXTLINE(readability-braces-around-statements)
+ if constexpr (dst_is_sub_sm) {
+ dst_state = std::string{sml::aux::string<typename submachine_type<typename T::dst_state>::type>{}.c_str()};
+ } else { // NOLINT(readability-misleading-indentation)
+ dst_state = std::string{sml::aux::string<typename T::dst_state>{}.c_str()};
+ }
+
+ const auto dst_internal = sml::aux::is_same<typename T::dst_state, sml::front::internal>::value;
+
+ const auto has_event = !sml::aux::is_same<typename T::event, sml::anonymous>::value;
+ const auto has_guard = !sml::aux::is_same<typename T::guard, sml::front::always>::value;
+ const auto has_action = !sml::aux::is_same<typename T::action, sml::front::none>::value;
+
+ if (has_event && has_action && sml::aux::is_same<typename T::action::type, sml::front::actions::defer>::value) {
+ do_indent(N);
+ std::cout << src_state << " : " << boost::sml::aux::get_type_name<typename T::event>() << " / defer" << std::endl;
+ return;
+ }
+
+ if (dst_state == "terminate") {
+ dst_state = "[*]";
+ }
+
+ if (T::initial) {
+ if (state_initialized) { // create an orthogonal section
+ do_indent(N);
+ std::cout << "--" << std::endl;
+ }
+
+ state_initialized = true;
+ do_indent(N);
+ std::cout << "[*] --> " << src_state << std::endl;
+ }
+
+ // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
+ if constexpr (src_is_sub_sm) {
+ auto already_in =
+ std::find(completed_submachines.begin(), completed_submachines.end(), src_state) != completed_submachines.end();
+ if (!already_in) {
+ completed_submachines.push_back(src_state);
+ constexpr int indent = N + 2;
+ do_indent(N);
+ std::cout << "state " << src_state << " {" << std::endl;
+ bool prev_state = state_initialized;
+ state_initialized = false;
+ dump_transitions<typename T::src_state::transitions>::template func<indent>();
+ do_indent(N);
+ std::cout << "}" << std::endl;
+ state_initialized = prev_state;
+ }
+ }
+
+ do_indent(N);
+ std::cout << src_state;
+ if (!dst_internal) {
+ std::cout << " --> " << dst_state;
+ }
+
+ if (has_event || has_guard || has_action) {
+ std::cout << " :";
+ }
+
+ if (has_event) {
+ std::cout << " " << std::string{sml::aux::string<typename T::event>{}.c_str()};
+ }
+
+ if (has_guard) {
+ std::cout << "\\n [";
+ print_guard<typename T::guard::type>::template func<0>();
+ std::cout << "]";
+ }
+
+ if (has_action) {
+ std::cout << " /\\n ";
+
+ if constexpr (is_seq_<typename T::action::type>::value) { // NOLINT(readability-braces-around-statements)
+ std::cout << "(";
+ print_seq_types<typename T::action::type>::template func<0>();
+ std::cout << ")";
+ } else { // NOLINT(readability-misleading-indentation)
+ std::cout << sml::aux::string<typename T::action::type>{}.c_str();
+ }
+ }
+
+ std::cout << std::endl;
+
+ // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
+ if constexpr (dst_is_sub_sm) {
+ auto already_in =
+ std::find(completed_submachines.begin(), completed_submachines.end(), dst_state) != completed_submachines.end();
+ if (!already_in) {
+ completed_submachines.push_back(dst_state);
+ constexpr int indent = N + 2;
+ do_indent(N);
+ std::cout << "state " << dst_state << " {" << std::endl;
+ bool prev_state = state_initialized;
+ state_initialized = false;
+ dump_transitions<typename T::dst_state::transitions>::template func<indent>();
+ do_indent(N);
+ std::cout << "}" << std::endl;
+ state_initialized = prev_state;
+ }
+ }
+}
+
+// this template allows iterating through the types in the parameter pack Ts...
+// I is the counter
+// INDENT is the current indentation level (for the state machine or sub-state machine)
+template <int INDENT, int I, class... Ts>
+void apply_dump_transition() {
+ // iteration is finished when I == the size of the parameter pack
+ constexpr auto param_pack_empty = (sizeof...(Ts) == I);
+ if constexpr (!param_pack_empty) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
+ // run the dump_transition function to print this sml::front::transition type
+ dump_transition<INDENT, NthTypeOf<I, Ts...>>();
+ // iteration isn't finished, keep going
+ apply_dump_transition<INDENT, I + 1, Ts...>();
+ }
+}
+
+// SFINAE type
+template <typename...>
+struct dump_transitions { // NOLINT(readability-identifier-naming)
+ template <int INDENT>
+ static void func() {}
+};
+
+// Partial specialization for sml::aux::type_list<Ts...>. This grants access to the
+// types inside the type list, which are sml::front::transition types, so they can
+// be passed to apply_dump_transition.
+template <typename... Ts>
+struct dump_transitions<typename sml::aux::type_list<Ts...>> { // NOLINT(readability-identifier-naming)
+ template <int INDENT>
+ static void func() {
+ apply_dump_transition<INDENT, 0, Ts...>();
+ }
+};
+
+// API to use to dump states:
+// example:
+// int main() { dump<plant_uml>(); }
+template <class T>
+void dump() noexcept {
+ std::cout << "@startuml" << std::endl << std::endl;
+ dump_transitions<typename sml::sm<T>::transitions>::template func<0>();
+ std::cout << std::endl << "@enduml" << std::endl;
+}