// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include #include #include "call/CellularCall.hpp" #include "log/log.hpp" #include "magic_enum.hpp" #include 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) { LOG_DEBUG("Called RING, status: %s", call.mode == sys::phone_modes::PhoneMode::Connected ? "true" : "false"); return call.mode == sys::phone_modes::PhoneMode::Connected; } } constexpr RING; struct Tethering { bool operator()(CallData &call) { return call.tethering == sys::phone_modes::Tethering::On; } } Tethering; struct HandleInit { void operator()(Dependencies &di, CallData &call) { call = CallData{}; call.mode = di.modem->getMode(); call.tethering = di.modem->getTethering(); 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.sentinel->HoldMinimumFrequency(bsp::CpuFrequencyMHz::Level_6); di.ringTimer->start(); } } constexpr HandleRing; struct HandleRingTimeout { void operator()(Dependencies &di, CallData &call) { di.multicast->notifyIncomingCall(); di.db->startCall(call.record); di.gui->notifyRING(); di.audio->play(); } } constexpr HandleRingTimeout; 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) { LOG_DEBUG("Called ClipDND_OK!"); 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) { LOG_DEBUG("Called HandleClipDND!"); handleClipCommon(di, clip, call); di.audio->play(); } } constexpr HandleClipDND; struct ClipDND_NOK { bool operator()(const call::event::CLIP &clip, Dependencies &di, CallData &call) { LOG_DEBUG("Called ClipDND_NOK!"); return call.mode == sys::phone_modes::PhoneMode::DoNotDisturb && not(di.modem->areCallsFromFavouritesEnabled() && di.db->isNumberInFavourites(clip.number)); } } constexpr ClipDND_NOK; struct HandleDND_Reject { void operator()(Dependencies &di, const call::event::CLIP &clip, CallData &call) { di.db->startCall(call.record); LOG_DEBUG("Called HandleDND_Reject!"); setNumber(call, clip.number); call.record.date = std::time(nullptr); call.record.type = CallType::CT_MISSED; #ifdef TARGET_RT1051 /* Workaround for a DND mode failed rejections. * Probably modem needs some time to process * the request internally, so there is a delay * required before issuing call rejecting. * The 750ms value has been obtained experimentally. */ constexpr auto modemProcessingTimeDelayMs = 750; vTaskDelay(pdMS_TO_TICKS(modemProcessingTimeDelayMs)); #endif di.modem->rejectCall(); di.db->endCall(call.record); } } constexpr HandleDND_Reject; /// when we have ongoing call - handle incoming 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->answerIncomingCall()) { throw std::runtime_error("modem failure!"); } di.audio->routingStart(); di.multicast->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.multicast->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.audio->routingStart(); di.db->startCall(call.record); di.multicast->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.multicast->notifyOutgoingCallAnswered(); 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.multicast->notifyCallEnded(); di.modem->rejectCall(); } } 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.multicast->notifyCallMissed(); di.modem->rejectCall(); }; } constexpr HandleMissedCall; struct HandleEndCall { void operator()(Dependencies &di, CallData &call) { di.audio->stop(); call.record.duration = di.timer->duration(); call.record.isRead = true; di.db->endCall(call.record); di.multicast->notifyCallEnded(); di.timer->stop(); di.modem->hangupCall(); } } constexpr HandleEndCall; struct HandleAudioRequest { void operator()(Dependencies &di, const call::event::AudioRequest &request) { const auto 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->setLoudspeakerOn(); break; case cellular::CallAudioEventRequest::EventType::LoudspeakerOff: di.audio->setLoudspeakerOff(); 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.multicast->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[RING and not Tethering] / HandleRing = "RingDelay"_s, "Idle"_s + boost::sml::event[Tethering or ClipDND_NOK] / HandleDND_Reject = "Idle"_s, "Idle"_s + boost::sml::event[ClipConnected] / HandleClipWithoutRing = "HaveId"_s, "Idle"_s + boost::sml::event[ClipDND_OK] / HandleClipDND = "HaveId"_s, // outgoing call: Pure is Ringing (called from: handleCellularRingingMessage) "Idle"_s + boost::sml::event / HandleStartCall = "Starting"_s, "Starting"_s + boost::sml::event / HandleAudioRequest = "Starting"_s, "Starting"_s + boost::sml::event / HandleEndCall = "Idle"_s, "Starting"_s + boost::sml::event / HandleEndCall = "Idle"_s, "Starting"_s + boost::sml::event / HandleStartedCall = "Ongoing"_s, "RingDelay"_s + boost::sml::event / HandleRingTimeout = "Ringing"_s, // here we do not need guard - RingDelay state is already guarded on entry "RingDelay"_s + boost::sml::event / HandleClipWithoutRing = "HaveId"_s, "Ringing"_s + boost::sml::event / HandleAnswerCall = "Ongoing"_s, "Ringing"_s + boost::sml::event / HandleRejectCall = "Idle"_s, "Ringing"_s + boost::sml::event / HandleRejectCall = "Idle"_s, "Ringing"_s + boost::sml::event[ClipConnected] / HandleClipConnected = "HaveId"_s, "Ringing"_s + boost::sml::event[ClipDND_OK] / HandleClipDND = "HaveId"_s, "Ringing"_s + boost::sml::event[ClipDND_NOK] / HandleDND_Reject = "Idle"_s, "HaveId"_s + boost::sml::event[CallIncoming] / HandleAnswerCall = "Ongoing"_s, "HaveId"_s + boost::sml::event / HandleMissedCall = "Idle"_s, "HaveId"_s + boost::sml::event / HandleRejectCall = "Idle"_s, "Ongoing"_s + boost::sml::event / HandleAddID = "Ongoing"_s, "Ongoing"_s + boost::sml::event / HandleCallTimer = "Ongoing"_s, "Ongoing"_s + boost::sml::event / HandleAudioRequest = "Ongoing"_s, // end call and reject when call is ongoing is same "Ongoing"_s + boost::sml::event / HandleEndCall = "Idle"_s, "Ongoing"_s + boost::sml::event / HandleEndCall = "Idle"_s, *("ExceptionsHandling"_s) + exception / ExceptionHandler = "Idle"_s, "ExceptionsHandling"_s + exception / 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> machine{logger, di, call}; }; } // namespace call