13#include "ns3/boolean.h"
15#include "ns3/wifi-net-device.h"
16#include "ns3/wifi-phy.h"
31 TypeId(
"ns3::AdvancedEmlsrManager")
35 .AddAttribute(
"UseNotifiedMacHdr",
36 "Whether to use the information about the MAC header of the MPDU "
37 "being received, if notified by the PHY.",
41 .AddAttribute(
"AllowUlTxopInRx",
42 "Whether a (main or aux) PHY is allowed to start an UL TXOP if "
43 "another PHY is receiving a PPDU (possibly starting a DL TXOP). "
44 "If this attribute is true, the PPDU may be dropped.",
48 .AddAttribute(
"InterruptSwitch",
49 "Whether the main PHY can be interrupted while switching to start "
50 "switching to another link.",
54 .AddAttribute(
"UseAuxPhyCca",
55 "Whether the CCA performed in the last PIFS interval by a non-TX "
56 "capable aux PHY should be used when the main PHY ends switching to "
57 "the aux PHY's link to determine whether TX can start or not (and what "
58 "bandwidth can be used for transmission) independently of whether the "
59 "aux PHY bandwidth is smaller than the main PHY bandwidth or not.",
63 .AddAttribute(
"SwitchMainPhyBackDelay",
64 "Duration of the timer started in case of non-TX capable aux PHY (that "
65 "does not switch link) when medium is sensed busy during the PIFS "
66 "interval preceding/following the main PHY switch end. When the timer "
67 "expires, the main PHY is switched back to the preferred link.",
88 for (
auto phy :
GetStaMac()->GetDevice()->GetPhys())
90 phy->TraceDisconnectWithoutContext(
103 for (
const auto& linkId :
GetStaMac()->GetLinkIds())
105 GetStaMac()->GetChannelAccessManager(linkId)->TraceDisconnectWithoutContext(
114 ->GetChannelAccessManager(emlsrLinkId)
115 ->TraceConnectWithoutContext(
128 for (
auto phy :
GetStaMac()->GetDevice()->GetPhys())
130 phy->TraceConnectWithoutContext(
142 for (
const auto id :
GetStaMac()->GetLinkIds())
144 if (
id != linkId &&
GetStaMac()->IsEmlsrLink(
id))
151 phy->GetState()->GetLastTime({WifiPhyState::RX}) ==
Simulator::Now());
156 if (
const auto& hdr = macHdr->get();
158 (hdr.GetAddr1().IsBroadcast() || hdr.GetAddr1() ==
GetEhtFem(
id)->GetAddress()))
160 return {
false, phy->GetDelayUntilIdle()};
165 if (phy && phy->IsReceivingPhyHeader())
172 return {
false, phy->GetDelayUntilIdle()};
177 if (phy && phy->IsStateRx())
186 return {
false, phy->GetDelayUntilIdle()};
192 auto ongoingRxInfo =
GetEhtFem(
id)->GetOngoingRxInfo();
203 if (!ongoingRxInfo.has_value())
206 "Main PHY should have MAC header info when in RX state");
211 const auto& txVector = ongoingRxInfo->get().txVector;
217 auto macHdrDuration =
DataRate(txVector.GetMode().GetDataRate(txVector))
219 const auto timeSinceRxStart =
221 return {
false,
Max(macHdrDuration - timeSinceRxStart,
Time{0})};
233 return {
false,
Time{0}};
236 return {
true,
Time{0}};
245 auto linkId =
GetStaMac()->GetLinkForPhy(phy);
246 if (!linkId.has_value())
252 auto& ongoingTxopEnd =
GetEhtFem(*linkId)->GetOngoingTxopEndEvent();
259 ongoingTxopEnd.Cancel();
306 "Aux PHY next link ID should have a value when interrupting a main PHY switch");
316 if (!GetEhtFem(linkId)->UsingOtherEmlsrLink())
318 SwitchMainPhy(GetMainPhyId(), false, DONT_RESET_BACKOFF, REQUEST_ACCESS);
325AdvancedEmlsrManager::GetDelayUnlessMainPhyTakesOverUlTxop(uint8_t linkId)
329 if (!m_interruptSwitching)
331 return DefaultEmlsrManager::GetDelayUnlessMainPhyTakesOverUlTxop(linkId);
334 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
335 auto state = mainPhy->GetState()->GetState();
338 state == WifiPhyState::IDLE || state == WifiPhyState::CCA_BUSY,
339 "Main PHY cannot be in state " << state);
341 auto timeToCtsEnd = GetTimeToCtsEnd(linkId);
342 auto switchingTime = mainPhy->GetChannelSwitchDelay();
344 if (switchingTime > timeToCtsEnd)
347 NS_LOG_DEBUG(
"Not enough time for main PHY to switch link (main PHY state: "
348 << mainPhy->GetState()->GetState() <<
")");
350 return {
false, timeToCtsEnd};
355 m_switchMainPhyOnRtsTx[linkId] = Simulator::Now();
357 return {
true,
Time{0}};
363 NS_LOG_FUNCTION(
this << phy->GetPhyId() << linkId << edca->GetAccessCategory());
365 const auto caManager = GetStaMac()->GetChannelAccessManager(linkId);
366 const auto pifs = phy->GetSifs() + phy->GetSlot();
368 const auto isBusy = caManager->IsBusy();
370 auto width = caManager->GetLargestIdlePrimaryChannel(pifs, Simulator::Now());
372 if (!isBusy && width > 0)
375 width = std::min(width, GetChannelForMainPhy(linkId).GetTotalWidth());
380 m_ccaLastPifs = Simulator::ScheduleNow([=,
this]() {
381 if (GetEhtFem(linkId)->HeFrameExchangeManager::StartTransmission(edca, width))
383 NotifyUlTxopStart(linkId);
385 else if (!m_switchAuxPhy)
388 SwitchMainPhyBackToPreferredLink(linkId);
396 edca->NotifyChannelReleased(linkId);
397 edca->StartAccessAfterEvent(linkId,
398 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
399 Txop::CHECK_MEDIUM_BUSY);
404 m_switchMainPhyBackEvent.Cancel();
405 m_switchMainPhyBackEvent = Simulator::Schedule(m_switchMainPhyBackDelay, [
this, linkId]() {
408 SwitchMainPhyBackToPreferredLink(linkId);
415AdvancedEmlsrManager::DoNotifyIcfReceived(uint8_t linkId)
418 m_switchMainPhyBackEvent.Cancel();
419 m_ccaLastPifs.Cancel();
423AdvancedEmlsrManager::DoNotifyUlTxopStart(uint8_t linkId)
426 m_switchMainPhyBackEvent.Cancel();
427 m_ccaLastPifs.Cancel();
431AdvancedEmlsrManager::RequestMainPhyToSwitch(uint8_t linkId,
AcIndex aci,
const Time& delay)
436 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
437 const auto mainPhyLinkId = GetStaMac()->GetLinkForPhy(mainPhy);
440 if (!mainPhyLinkId.has_value())
447 if (m_ccaLastPifs.IsPending() || m_switchMainPhyBackEvent.IsPending())
449 NS_LOG_DEBUG(
"Main PHY is trying to get access on another link");
453 switch (mainPhy->GetState()->GetState())
455 case WifiPhyState::IDLE:
458 case WifiPhyState::CCA_BUSY:
461 if (mainPhy->IsReceivingPhyHeader() && !m_allowUlTxopInRx)
463 NS_LOG_DEBUG(
"Main PHY receiving PHY header and AllowUlTxopInRx is false");
467 case WifiPhyState::RX:
468 if (
auto macHdr = GetEhtFem(*mainPhyLinkId)->GetReceivedMacHdr())
472 if (
const auto& hdr = macHdr->get();
473 !m_useNotifiedMacHdr ||
474 (hdr.IsTrigger() && (hdr.GetAddr1().IsBroadcast() ||
475 hdr.GetAddr1() == GetEhtFem(*mainPhyLinkId)->GetAddress())))
477 NS_LOG_DEBUG(
"Receiving an ICF or cannot use MAC header information");
483 else if (!m_allowUlTxopInRx)
485 NS_LOG_DEBUG(
"Receiving PSDU, no MAC header information, AllowUlTxopInRx is false");
490 NS_LOG_DEBUG(
"Cannot request main PHY to switch when in state "
491 << mainPhy->GetState()->GetState());
501 auto requestSwitch =
false;
502 const auto now = Simulator::Now();
506 if (
auto edca = GetStaMac()->GetQosTxop(acIndex);
507 acIndex >= aci && edca->HasFramesToTransmit(linkId))
509 requestSwitch =
true;
511 const auto backoffEnd =
512 GetStaMac()->GetChannelAccessManager(*mainPhyLinkId)->GetBackoffEndFor(edca);
514 <<
" on preferred link: " << backoffEnd.As(Time::US));
516 if (
const auto minDelay = std::max(delay,
517 mainPhy->GetChannelSwitchDelay() +
518 GetStaMac()->GetWifiPhy(linkId)->GetPifs());
519 backoffEnd <= now + minDelay && edca->HasFramesToTransmit(*mainPhyLinkId))
521 requestSwitch =
false;
527 return requestSwitch;
531AdvancedEmlsrManager::SwitchMainPhyIfTxopGainedByAuxPhy(uint8_t linkId,
AcIndex aci)
536 "This function should only be called if aux PHY is not TX capable");
537 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
539 if (mainPhy->IsStateSwitching() && m_mainPhySwitchInfo.to == linkId)
546 NS_LOG_DEBUG(
"Main PHY is already switching to link " << +linkId);
550 if (RequestMainPhyToSwitch(linkId, aci,
Time{0}))
552 const auto auxPhy = GetStaMac()->GetWifiPhy(linkId);
553 const auto pifs = auxPhy->GetSifs() + auxPhy->GetSlot();
556 if (m_useAuxPhyCca || GetChannelForAuxPhy(linkId).GetTotalWidth() >=
557 GetChannelForMainPhy(linkId).GetTotalWidth())
560 NS_LOG_DEBUG(
"Schedule CCA check at the end of main PHY switch");
561 m_ccaLastPifs = Simulator::Schedule(mainPhy->GetChannelSwitchDelay(),
562 &AdvancedEmlsrManager::CheckNavAndCcaLastPifs,
566 GetStaMac()->GetQosTxop(aci));
571 NS_LOG_DEBUG(
"Schedule CCA check a PIFS after the end of main PHY switch");
572 m_ccaLastPifs = Simulator::Schedule(mainPhy->GetChannelSwitchDelay() + pifs,
573 &AdvancedEmlsrManager::CheckNavAndCcaLastPifs,
577 GetStaMac()->GetQosTxop(aci));
581 SwitchMainPhy(linkId,
false, RESET_BACKOFF, DONT_REQUEST_ACCESS);
604 if (m_ccaLastPifs.IsPending() || m_switchMainPhyBackEvent.IsPending())
606 delay = std::max(Simulator::GetDelayLeft(m_ccaLastPifs),
607 Simulator::GetDelayLeft(m_switchMainPhyBackEvent));
609 else if (mainPhy->IsStateSwitching() || mainPhy->IsStateCcaBusy() || mainPhy->IsStateRx())
611 delay = mainPhy->GetDelayUntilIdle();
615 NS_LOG_DEBUG(
"Main PHY state is " << mainPhy->GetState()->GetState());
623 auto edca = GetStaMac()->GetQosTxop(aci);
624 edca->NotifyChannelReleased(linkId);
627 << +linkId <<
" at time " << (Simulator::Now() + delay).As(Time::NS));
628 Simulator::Schedule(delay, [=]() {
629 edca->StartAccessAfterEvent(linkId,
630 Txop::DIDNT_HAVE_FRAMES_TO_TRANSMIT,
631 Txop::CHECK_MEDIUM_BUSY);
636AdvancedEmlsrManager::SwitchMainPhyIfTxopToBeGainedByAuxPhy(uint8_t linkId,
642 if (m_auxPhyTxCapable)
650 NS_LOG_DEBUG(
"Do nothing if delay is not strictly positive");
654 auto mainPhy = GetStaMac()->GetDevice()->GetPhy(m_mainPhyId);
655 auto phy = GetStaMac()->GetWifiPhy(linkId);
657 if (!phy || phy == mainPhy)
659 NS_LOG_DEBUG(
"No aux PHY is operating on link " << +linkId);
663 if (!RequestMainPhyToSwitch(linkId, aci, delay))
665 NS_LOG_DEBUG(
"Chosen not to request the main PHY to switch");
666 if (
const auto untilIdle = mainPhy->GetDelayUntilIdle();
667 untilIdle.IsStrictlyPositive() && untilIdle < delay)
670 Simulator::Schedule(untilIdle,
671 &AdvancedEmlsrManager::SwitchMainPhyIfTxopToBeGainedByAuxPhy,
681 SwitchMainPhy(linkId,
false, RESET_BACKOFF, DONT_REQUEST_ACCESS);
685 Simulator::Schedule(mainPhy->GetChannelSwitchDelay(), [=,
this]() {
686 const auto edca = GetStaMac()->GetQosTxop(aci);
687 const auto pifs = GetStaMac()->GetWifiPhy(linkId)->GetPifs();
688 if (GetStaMac()->GetChannelAccessManager(linkId)->GetBackoffEndFor(edca) <=
689 Simulator::Now() + pifs)
692 NS_LOG_DEBUG(
"Schedule CCA check a PIFS after the end of main PHY switch");
693 m_ccaLastPifs = Simulator::Schedule(pifs,
694 &AdvancedEmlsrManager::CheckNavAndCcaLastPifs,
705 const auto minDelay =
707 mainPhy->GetChannelSwitchDelay() + GetStaMac()->GetWifiPhy(linkId)->GetPifs());
708 m_switchMainPhyBackEvent.Cancel();
709 m_switchMainPhyBackEvent =
710 Simulator::Schedule(minDelay + m_switchMainPhyBackDelay, [
this, linkId]() {
713 SwitchMainPhyBackToPreferredLink(linkId);
AdvancedEmlsrManager is an advanced EMLSR manager.
std::pair< bool, Time > DoGetDelayUntilAccessRequest(uint8_t linkId) override
Subclasses have to provide an implementation for this method, that is called by the base class when t...
bool m_useAuxPhyCca
whether the CCA performed in the last PIFS interval by a non-TX capable aux PHY should be used when t...
~AdvancedEmlsrManager() override
void SwitchMainPhyIfTxopToBeGainedByAuxPhy(uint8_t linkId, AcIndex aci, const Time &delay)
This method is called when the given AC of the EMLSR client is expected to get channel access in the ...
bool m_allowUlTxopInRx
whether a (main or aux) PHY is allowed to start an UL TXOP if another PHY is receiving a PPDU
void DoNotifyTxopEnd(uint8_t linkId) override
Notify the subclass of the end of a TXOP on the given link.
void NotifyEmlsrModeChanged() override
Notify subclass that EMLSR mode changed.
static TypeId GetTypeId()
Get the type ID.
bool m_useNotifiedMacHdr
whether to use the information about the MAC header of the MPDU being received (if notified by the PH...
Time m_switchMainPhyBackDelay
duration of the timer started in case of non-TX capable aux PHY when medium is sensed busy during the...
void DoDispose() override
Destructor implementation.
void DoSetWifiMac(Ptr< StaWifiMac > mac) override
Allow subclasses to take actions when the MAC is set.
void ReceivedMacHdr(Ptr< WifiPhy > phy, const WifiMacHeader &macHdr, const WifiTxVector &txVector, Time psduDuration)
Possibly take actions when notified of the MAC header of the MPDU being received by the given PHY.
EventId m_ccaLastPifs
event scheduled in case of non-TX capable aux PHY to determine whether TX can be started based on whe...
bool m_interruptSwitching
whether a main PHY switching can be interrupted to start switching to another link
AttributeValue implementation for Boolean.
Class for representing data rates.
Time CalculateBytesTxTime(uint32_t bytes) const
Calculate transmission time.
DefaultEmlsrManager is the default EMLSR manager.
void NotifyEmlsrModeChanged() override
Notify subclass that EMLSR mode changed.
Ptr< WifiPhy > m_auxPhyToReconnect
Aux PHY the ChannelAccessManager of the link on which the main PHY is operating has to connect a list...
bool m_switchAuxPhy
whether Aux PHY should switch channel to operate on the link on which the Main PHY was operating befo...
MainPhySwitchInfo m_mainPhySwitchInfo
main PHY switch info
bool m_auxPhyTxCapable
whether Aux PHYs are capable of transmitting PPDUs
Ptr< EhtFrameExchangeManager > GetEhtFem(uint8_t linkId) const
void NotifyTxopEnd(uint8_t linkId, bool ulTxopNotStarted=false, bool ongoingDlTxop=false)
Notify the end of a TXOP on the given link.
void SwitchMainPhy(uint8_t linkId, bool noSwitchDelay, bool resetBackoff, bool requestAccess)
Switch channel on the Main PHY so that it operates on the given link.
uint8_t m_mainPhyId
ID of main PHY (position in the vector of PHYs held by WifiNetDevice)
const std::set< uint8_t > & GetEmlsrLinks() const
static constexpr bool REQUEST_ACCESS
request channel access when PHY switch ends
Ptr< StaWifiMac > GetStaMac() const
uint8_t GetMainPhyId() const
static constexpr bool DONT_RESET_BACKOFF
do not reset backoff on main PHY switch
bool IsPending() const
This method is syntactic sugar for !IsExpired().
virtual void DoDispose()
Destructor implementation.
Smart pointer class similar to boost::intrusive_ptr.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
static Time Now()
Return the current simulation virtual time.
static EventId ScheduleNow(FUNC f, Ts &&... args)
Schedule an event to expire Now.
Simulation virtual time values and global simulation resolution.
TimeWithUnit As(const Unit unit=Time::AUTO) const
Attach a unit to a Time, to facilitate output in a specific unit.
bool IsStrictlyPositive() const
Exactly equivalent to t > 0.
AttributeValue implementation for Time.
a unique identifier for an interface.
TypeId SetParent(TypeId tid)
Set the parent TypeId.
This class mimics the TXVECTOR which is to be passed to the PHY in order to define the parameters whi...
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Ptr< const AttributeChecker > MakeBooleanChecker()
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Ptr< const AttributeAccessor > MakeTimeAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
#define NS_ABORT_MSG_UNLESS(cond, msg)
Abnormal program termination if a condition is false, with a message.
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
#define NS_LOG_FUNCTION_NOARGS()
Output the name of the function.
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
AcIndex
This enumeration defines the Access Categories as an enumeration with values corresponding to the AC ...
Every class exported by the ns3 library is enclosed in the ns3 namespace.
@ CCA_BUSY
The PHY layer has sense the medium busy through the CCA mechanism.
Callback< R, Args... > MakeCallback(R(T::*memPtr)(Args...), OBJ objPtr)
Build Callbacks for class method members which take varying numbers of arguments and potentially retu...
const std::map< AcIndex, WifiAc > wifiAcList
Map containing the four ACs in increasing order of priority (according to Table 10-1 "UP-to-AC Mappin...
Time end
end of channel switching
uint8_t from
ID of the link which the main PHY is/has been leaving.