Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#local files
implementation_plan*
# local and generated files not to be committed
doc/src/showcases
doc/src/tutorials
Expand Down
37 changes: 37 additions & 0 deletions examples/wireless/channelwidths/ChannelWidthsNetwork.ned
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// Copyright (C) 2026 OpenSim Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//

package inet.examples.wireless.channelwidths;

import inet.networks.base.WirelessNetworkBase;
import inet.node.inet.WirelessHost;
import inet.node.wireless.AccessPoint;

network ChannelWidthsNetwork extends WirelessNetworkBase
{
parameters:
@display("bgb=650,450");
submodules:
ap: AccessPoint {
@display("p=250,200;r=,,#707070");
wlan[*].mgmt.typename = "Ieee80211MgmtApSimplified";
}
sta1: WirelessHost {
@display("p=400,150;r=,,#707070");
wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified";
wlan[*].agent.typename = "";
}
sta2: WirelessHost {
@display("p=400,250;r=,,#707070");
wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified";
wlan[*].agent.typename = "";
}
interferer: WirelessHost {
@display("p=400,350;r=,,#707070");
wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified";
wlan[*].agent.typename = "";
}
}
72 changes: 72 additions & 0 deletions examples/wireless/channelwidths/omnetpp.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
[General]
network = ChannelWidthsNetwork
sim-time-limit = 2s

# AP and STA configuration
**.ap.wlan[*].address = "10:00:00:00:00:01"
**.sta1.wlan[*].mgmt.accessPointAddress = "10:00:00:00:00:01"
**.sta2.wlan[*].mgmt.accessPointAddress = "10:00:00:00:00:01"
**.interferer.wlan[*].mgmt.accessPointAddress = "10:00:00:00:00:01"

# Radio medium parameters
**.radioMedium.backgroundNoise.power = -110dBm
**.radioMedium.analogModel.ignorePartialInterference = true
**.radioMedium.sameTransmissionStartTimeCheck = "ignore"

# Wi-Fi physical parameters
**.wlan[*].opMode = "n(mixed-2.4Ghz)"
**.wlan[*].radio.transmitter.power = 20mW
**.wlan[*].radio.receiver.sensitivity = -85dBm
**.wlan[*].radio.receiver.energyDetection = -85dBm
**.wlan[*].radio.receiver.snirThreshold = 4dB

# Application: UDP traffic between STA1 and STA2
*.sta1.numApps = 1
*.sta1.app[0].typename = "UdpBasicApp"
*.sta1.app[0].destAddresses = "sta2"
*.sta1.app[0].destPort = 5000
*.sta1.app[0].messageLength = 1000B
*.sta1.app[0].sendInterval = 1ms
*.sta1.app[0].startTime = 0.1s
*.sta1.app[0].stopTime = 1.9s

*.sta2.numApps = 1
*.sta2.app[0].typename = "UdpSink"
*.sta2.app[0].localPort = 5000

# Base HT20 config
[Config Ht20MHz]
description = "IEEE 802.11n 20 MHz operation on 2.4 GHz channel 0"
**.wlan[*].radio.bandwidth = 20MHz
**.wlan[*].radio.channelNumber = 0
**.wlan[*].htSecondaryChannelOffset = "none"

# HT40+ (secondary above)
[Config Ht40MHzSecondaryAbove]
description = "IEEE 802.11n 40 MHz operation with secondary channel above (+20 MHz)"
**.wlan[*].radio.bandwidth = 40MHz
**.wlan[*].radio.channelNumber = 0
**.wlan[*].htSecondaryChannelOffset = "above"

# HT40- (secondary below)
[Config Ht40MHzSecondaryBelow]
description = "IEEE 802.11n 40 MHz operation with secondary channel below (-20 MHz)"
**.wlan[*].radio.bandwidth = 40MHz
**.wlan[*].radio.channelNumber = 4
**.wlan[*].htSecondaryChannelOffset = "below"

# HT40+ with interferer on secondary channel
[Config Ht40MHzSecondaryAboveWithInterferer]
description = "HT40+ with secondary channel interferer causing EDCA backoff restart"
extends = Ht40MHzSecondaryAbove
*.interferer.numApps = 1
*.interferer.app[0].typename = "UdpBasicApp"
*.interferer.app[0].destAddresses = "sta2"
*.interferer.app[0].destPort = 6000
*.interferer.app[0].messageLength = 500B
*.interferer.app[0].sendInterval = 2ms
*.interferer.app[0].startTime = 0.2s
*.interferer.app[0].stopTime = 1.8s
*.interferer.wlan[*].radio.bandwidth = 20MHz
*.interferer.wlan[*].radio.channelNumber = 4
*.interferer.wlan[*].htSecondaryChannelOffset = "none"
3 changes: 3 additions & 0 deletions src/inet/linklayer/ieee80211/Ieee80211Interface.ned
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,16 @@ module Ieee80211Interface extends NetworkInterface like IWirelessInterface
string interfaceTableModule;
string energySourceModule = default("");
string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac") = default("g(mixed)");
string htSecondaryChannelOffset @enum("none", "above", "below") = default("none");
string address @mutable = default("auto"); // MAC address as hex string (12 hex digits), or
// "auto". "auto" values will be replaced by
// a generated MAC address in init stage 0.
string protocol = default("");
double bitrate @unit(bps) = default(-1bps);
**.opMode = this.opMode;
**.bitrate = this.bitrate;
radio.htSecondaryChannelOffset = default(this.htSecondaryChannelOffset);
mib.htSecondaryChannelOffset = default(this.htSecondaryChannelOffset);
mac.modeSet = default(this.opMode);
mac.*.rateSelection.dataFrameBitrate = default(this.bitrate);
*.macModule = default(absPath(".mac"));
Expand Down
15 changes: 13 additions & 2 deletions src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "inet/linklayer/ieee80211/mac/contract/IRx.h"
#include "inet/linklayer/ieee80211/mac/contract/ITx.h"
#include "inet/networklayer/contract/IInterfaceTable.h"
#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211CcaProvider.h"
#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo_m.h"
#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h"

Expand Down Expand Up @@ -66,11 +67,14 @@ void Ieee80211Mac::initialize(int stage)
ds = check_and_cast<IDs *>(getSubmodule("ds"));
rx = check_and_cast<IRx *>(getSubmodule("rx"));
tx = check_and_cast<ITx *>(getSubmodule("tx"));
auto ccaProvider = dynamic_cast<IIeee80211CcaProvider *>(radio.get());
if (ccaProvider != nullptr) {
radioModule->subscribe(IIeee80211CcaProvider::ccaStateChangedSignal, this);
rx->ccaStateChanged(ccaProvider->getCcaSnapshot());
}
emit(modesetChangedSignal, modeSet);
if (isUp())
initializeRadioMode();
rx = check_and_cast<IRx *>(getSubmodule("rx"));
tx = check_and_cast<ITx *>(getSubmodule("tx"));
dcf = check_and_cast<Dcf *>(getSubmodule("dcf"));
hcf = check_and_cast_nullable<Hcf *>(getSubmodule("hcf"));
if (mib->qos && !hcf)
Expand Down Expand Up @@ -327,6 +331,13 @@ void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, intva
}
}

void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, cObject *value, cObject *details)
{
Enter_Method("%s", cComponent::getSignalName(signalID));
if (signalID == IIeee80211CcaProvider::ccaStateChangedSignal)
rx->ccaStateChanged(*check_and_cast<Ieee80211CcaSnapshot *>(value));
}

void Ieee80211Mac::configureRadioMode(IRadio::RadioMode radioMode)
{
if (radio->getRadioMode() != radioMode) {
Expand Down
1 change: 1 addition & 0 deletions src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class INET_API Ieee80211Mac : public MacProtocolBase
virtual void initializeRadioMode();

virtual void receiveSignal(cComponent *source, simsignal_t signalID, intval_t value, cObject *details) override;
virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *value, cObject *details) override;
using MacProtocolBase::receiveSignal;
virtual void configureRadioMode(physicallayer::IRadio::RadioMode radioMode);
virtual void configureNetworkInterface() override;
Expand Down
25 changes: 24 additions & 1 deletion src/inet/linklayer/ieee80211/mac/Rx.cc
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,36 @@ void Rx::recomputeMediumFree()
{
bool oldMediumFree = mediumFree;
// note: the duration of mode switching (rx-to-tx or tx-to-rx) should also count as busy
mediumFree = receptionState == IRadio::RECEPTION_STATE_IDLE && transmissionState == IRadio::TRANSMISSION_STATE_UNDEFINED && !endNavTimer->isScheduled();
bool primaryPhysicallyIdle = (receptionState != IRadio::RECEPTION_STATE_RECEIVING) &&
(!ht40Cca ? (receptionState == IRadio::RECEPTION_STATE_IDLE) : !primaryCcaBusy);
mediumFree = primaryPhysicallyIdle && transmissionState == IRadio::TRANSMISSION_STATE_UNDEFINED && !endNavTimer->isScheduled();
if (mediumFree != oldMediumFree) {
for (auto contention : contentions)
contention->mediumStateChanged(mediumFree);
}
}

bool Rx::isSecondaryChannelIdleFor(simtime_t interval) const
{
return !ht40Cca || (!secondaryCcaBusy && secondaryCcaIdleSince >= SIMTIME_ZERO &&
simTime() - secondaryCcaIdleSince >= interval);
}

void Rx::ccaStateChanged(const Ieee80211CcaSnapshot& snapshot)
{
Enter_Method("ccaStateChanged");
bool wasHt40Cca = ht40Cca;
bool wasSecondaryBusy = secondaryCcaBusy;
ht40Cca = snapshot.isHt40();
primaryCcaBusy = snapshot.isPrimaryBusy();
secondaryCcaBusy = snapshot.isSecondaryBusy();
if (!ht40Cca || secondaryCcaBusy)
secondaryCcaIdleSince = -1;
else if (!wasHt40Cca || wasSecondaryBusy)
secondaryCcaIdleSince = simTime();
recomputeMediumFree();
}

void Rx::receptionStateChanged(IRadio::ReceptionState state)
{
Enter_Method("receptionStateChanged");
Expand Down
6 changes: 6 additions & 0 deletions src/inet/linklayer/ieee80211/mac/Rx.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class INET_API Rx : public SimpleModule, public IRx
physicallayer::IRadio::TransmissionState transmissionState = physicallayer::IRadio::TRANSMISSION_STATE_UNDEFINED;
physicallayer::IRadioSignal::SignalPart receivedPart = physicallayer::IRadioSignal::SIGNAL_PART_NONE;
bool mediumFree = true; // cached state
bool ht40Cca = false;
bool primaryCcaBusy = false;
bool secondaryCcaBusy = false;
simtime_t secondaryCcaIdleSince = -1;

protected:
virtual int numInitStages() const override { return NUM_INIT_STAGES; }
Expand All @@ -53,7 +57,9 @@ class INET_API Rx : public SimpleModule, public IRx

virtual bool isReceptionInProgress() const override;
virtual bool isMediumFree() const override { return mediumFree; }
virtual bool isSecondaryChannelIdleFor(simtime_t interval) const override;
virtual void receptionStateChanged(physicallayer::IRadio::ReceptionState newReceptionState) override;
virtual void ccaStateChanged(const physicallayer::Ieee80211CcaSnapshot& snapshot) override;
virtual void transmissionStateChanged(physicallayer::IRadio::TransmissionState transmissionState) override;
virtual void receivedSignalPartChanged(physicallayer::IRadioSignal::SignalPart part) override;
virtual bool lowerFrameReceived(Packet *packet) override;
Expand Down
11 changes: 11 additions & 0 deletions src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ void Dcaf::releaseChannel(IChannelAccess::ICallback *callback)
EV_INFO << "Channel released.\n";
}

void Dcaf::restartChannelAccess(IChannelAccess::ICallback *callback)
{
Enter_Method("restartChannelAccess");
ASSERT(owning && (callback == nullptr || this->callback == callback));
owning = false;
emit(channelOwnershipChangedSignal, owning);
// IEEE Std 802.11-2024, 11.15.9 item b): retain the current CW and draw a
// fresh random backoff without updating any retry counter.
contention->startContention(cw, ifs, eifs, slotTime, this);
}

void Dcaf::requestChannel(IChannelAccess::ICallback *callback)
{
Enter_Method("requestChannel");
Expand Down
1 change: 1 addition & 0 deletions src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class INET_API Dcaf : public IChannelAccess, public IContention::ICallback, publ
// IChannelAccess::ICallback
virtual void requestChannel(IChannelAccess::ICallback *callback) override;
virtual void releaseChannel(IChannelAccess::ICallback *callback) override;
virtual void restartChannelAccess(IChannelAccess::ICallback *callback);

// IContention::ICallback
virtual void channelAccessGranted() override;
Expand Down
11 changes: 11 additions & 0 deletions src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@ void Edcaf::releaseChannel(IChannelAccess::ICallback *callback)
EV_INFO << "Channel released.\n";
}

void Edcaf::restartChannelAccess(IChannelAccess::ICallback *callback)
{
Enter_Method("restartChannelAccess");
ASSERT(owning && (callback == nullptr || this->callback == callback));
owning = false;
emit(channelOwnershipChangedSignal, owning);
// IEEE Std 802.11-2024, 11.15.9 item b): retain CW[AC], select a new
// random backoff, and leave retry counters and queued frames unchanged.
contention->startContention(cw, ifs, eifs, slotTime, this);
}

void Edcaf::requestChannel(IChannelAccess::ICallback *callback)
{
Enter_Method("requestChannel");
Expand Down
1 change: 1 addition & 0 deletions src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class INET_API Edcaf : public IChannelAccess, public IContention::ICallback, pub
// IChannelAccess
virtual void requestChannel(IChannelAccess::ICallback *callback) override;
virtual void releaseChannel(IChannelAccess::ICallback *callback) override;
virtual void restartChannelAccess(IChannelAccess::ICallback *callback);

// IContention::ICallback
virtual void channelAccessGranted() override;
Expand Down
3 changes: 2 additions & 1 deletion src/inet/linklayer/ieee80211/mac/contention/Contention.cc
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,9 @@ void Contention::handleMessage(cMessage *msg)
else if (msg == channelGrantedEvent) {
EV_INFO << "Channel granted: startTime = " << startTime << std::endl;
emit(channelAccessGrantedSignal, this);
callback->channelAccessGranted();
auto cb = callback;
callback = nullptr;
cb->channelAccessGranted();
}
else
throw cRuntimeError("Unknown msg");
Expand Down
3 changes: 3 additions & 0 deletions src/inet/linklayer/ieee80211/mac/contract/IRx.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h"
#include "inet/linklayer/ieee80211/mac/contract/IContention.h"
#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h"
#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211CcaProvider.h"

namespace inet {
namespace ieee80211 {
Expand All @@ -35,13 +36,15 @@ class INET_API IRx

// from Contention
virtual bool isMediumFree() const = 0;
virtual bool isSecondaryChannelIdleFor(simtime_t interval) const = 0;
virtual void frameTransmitted(simtime_t durationField) = 0;

// from Coordination functions
virtual void registerContention(IContention *contention) = 0;

// events
virtual void receptionStateChanged(physicallayer::IRadio::ReceptionState state) = 0;
virtual void ccaStateChanged(const physicallayer::Ieee80211CcaSnapshot& snapshot) = 0;
virtual void transmissionStateChanged(physicallayer::IRadio::TransmissionState state) = 0;
virtual void receivedSignalPartChanged(physicallayer::IRadioSignal::SignalPart part) = 0;
virtual bool lowerFrameReceived(Packet *packet) = 0;
Expand Down
4 changes: 3 additions & 1 deletion src/inet/linklayer/ieee80211/mac/coordinationfunction/Dcf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "inet/linklayer/ieee80211/mac/framesequence/DcfFs.h"
#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h"
#include "inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.h"
#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h"

namespace inet {
namespace ieee80211 {
Expand Down Expand Up @@ -164,7 +165,8 @@ void Dcf::transmitFrame(Packet *packet, simtime_t ifs)
{
Enter_Method("transmitFrame");
const auto& header = packet->peekAtFront<Ieee80211MacHeader>();
auto mode = rateSelection->computeMode(packet, header);
auto modeReq = packet->findTag<Ieee80211ModeReq>();
auto mode = modeReq != nullptr ? modeReq->getMode() : rateSelection->computeMode(packet, header);
RateSelection::setFrameMode(packet, header, mode);
emit(IRateSelection::datarateSelectedSignal, mode->getDataMode()->getNetBitrate().get<bps>(), packet);
EV_DEBUG << "Datarate for " << packet->getName() << " is set to " << mode->getDataMode()->getNetBitrate() << ".\n";
Expand Down
28 changes: 27 additions & 1 deletion src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h"
#include "inet/linklayer/ieee80211/mac/framesequence/HcfFs.h"
#include "inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.h"
#include "inet/physicallayer/wireless/ieee80211/mode/Ieee80211HtMode.h"
#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h"

namespace inet {
Expand Down Expand Up @@ -193,20 +194,45 @@ void Hcf::processLowerFrame(Packet *packet, const Ptr<const Ieee80211MacHeader>&
}
}

bool Hcf::shouldRestartHt40ChannelAccess(Edcaf *edcaf)
{
Packet *frame = edcaf->getInProgressFrames()->getFrameToTransmit();
if (frame == nullptr)
return false;
const auto& header = frame->peekAtFront<Ieee80211MacHeader>();
auto modeReq = frame->findTag<Ieee80211ModeReq>();
auto mode = modeReq != nullptr ? modeReq->getMode() : rateSelection->computeMode(frame, header, edcaf->getTxopProcedure());
if (mode == nullptr || dynamic_cast<const Ieee80211HtMode *>(mode) == nullptr ||
mode->getDataMode()->getBandwidth() != MHz(40))
return false;
// IEEE Std 802.11-2024, 11.15.9 item b):
// Secondary channel must be idle during an interval of DIFS for the 2.4 GHz band
// and PIFS for the 5 GHz band immediately preceding the expiration of the backoff counter.
// If the secondary channel was busy during this interval, invoke the backoff procedure with the current CW[AC].
bool is24GHz = modeSet != nullptr && strstr(modeSet->getName(), "2.4Ghz") != nullptr;
simtime_t requiredIdle = modeSet->getSifsTime() + (is24GHz ? 2 : 1) * modeSet->getSlotTime();
return !rx->isSecondaryChannelIdleFor(requiredIdle);
}

void Hcf::channelGranted(IChannelAccess *channelAccess)
{
Enter_Method("channelGranted");
auto edcaf = check_and_cast<Edcaf *>(channelAccess);
if (edcaf) {
AccessCategory ac = edcaf->getAccessCategory();
EV_DETAIL << "Channel access granted to the " << printAccessCategory(ac) << " queue" << std::endl;
edcaf->getTxopProcedure()->startTxop(ac);
auto internallyCollidedEdcafs = edca->getInternallyCollidedEdcafs();
if (internallyCollidedEdcafs.size() > 0) {
EV_INFO << "Internal collision happened with the following queues:" << std::endl;
handleInternalCollision(internallyCollidedEdcafs);
emit(edcaCollisionDetectedSignal, (unsigned long)internallyCollidedEdcafs.size());
}
if (shouldRestartHt40ChannelAccess(edcaf)) {
EV_INFO << "Secondary channel was busy during required interval before channel access for HT40 transmission, restarting backoff.\n";
edcaf->restartChannelAccess(this);
return;
}
edcaf->getTxopProcedure()->startTxop(ac);
startFrameSequence(ac);
}
else
Expand Down
Loading
Loading