mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-04-18 22:18:38 -04:00
382 lines
14 KiB
C++
382 lines
14 KiB
C++
// Copyright (c) 2017-2023, 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)
|
|
{
|
|
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<evt::RING>[RING and not Tethering] / HandleRing = "RingDelay"_s,
|
|
|
|
"Idle"_s + boost::sml::event<evt::CLIP>[Tethering or ClipDND_NOK] / HandleDND_Reject = "Idle"_s,
|
|
|
|
"Idle"_s + boost::sml::event<evt::CLIP>[ClipConnected] / HandleClipWithoutRing = "HaveId"_s,
|
|
"Idle"_s + boost::sml::event<evt::CLIP>[ClipDND_OK] / HandleClipDND = "HaveId"_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::AudioRequest> / HandleAudioRequest = "Starting"_s,
|
|
"Starting"_s + boost::sml::event<evt::Ended> / HandleEndCall = "Idle"_s,
|
|
"Starting"_s + boost::sml::event<evt::Reject> / HandleEndCall = "Idle"_s,
|
|
"Starting"_s + boost::sml::event<evt::Answer> / HandleStartedCall = "Ongoing"_s,
|
|
|
|
"RingDelay"_s + boost::sml::event<evt::RingTimeout> / HandleRingTimeout = "Ringing"_s,
|
|
// here we do not need guard - RingDelay state is already guarded on entry
|
|
"RingDelay"_s + boost::sml::event<evt::CLIP> / HandleClipWithoutRing = "HaveId"_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,
|
|
|
|
*("ExceptionsHandling"_s) + exception<std::runtime_error> / ExceptionHandler = "Idle"_s,
|
|
"ExceptionsHandling"_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
|