Skip to content

Commit 7ca2e03

Browse files
Ieee80211: add support for HT primary and secondary channels (HT40- and HT40+)
- Added primary, secondary, and bonded center frequency calculations in Ieee80211Channel with HT40+ and HT40- offsets per IEEE Std 802.11-2024 Table 9-134. - Implemented multi-threshold HT CCA sensing (-82 dBm HT20, -79 dBm HT40 bonded, -62 dBm energy detection) in Ieee80211Receiver per Clause 19.3.19.6. - Added IIeee80211CcaProvider interface and ccaStateChangedSignal in Ieee80211Radio for subchannel busy notification. - Implemented secondary channel DIFS idle sensing and EDCA channel access restart in Hcf and Edcaf per Clause 11.15.9. - Added unit tests for channel geometry and CCA sensitivities, and example scenario demonstrating HT20, HT40+, HT40-, and secondary interferer.
1 parent 7b0a6de commit 7ca2e03

31 files changed

Lines changed: 792 additions & 49 deletions
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// Copyright (C) 2026 OpenSim Ltd.
3+
//
4+
// SPDX-License-Identifier: LGPL-3.0-or-later
5+
//
6+
7+
package inet.examples.wireless.channelwidths;
8+
9+
import inet.networks.base.WirelessNetworkBase;
10+
import inet.node.inet.WirelessHost;
11+
import inet.node.wireless.AccessPoint;
12+
13+
network ChannelWidthsNetwork extends WirelessNetworkBase
14+
{
15+
parameters:
16+
@display("bgb=650,450");
17+
submodules:
18+
ap: AccessPoint {
19+
@display("p=250,200;r=,,#707070");
20+
wlan[*].mgmt.typename = "Ieee80211MgmtApSimplified";
21+
}
22+
sta1: WirelessHost {
23+
@display("p=400,150;r=,,#707070");
24+
wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified";
25+
wlan[*].agent.typename = "";
26+
}
27+
sta2: WirelessHost {
28+
@display("p=400,250;r=,,#707070");
29+
wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified";
30+
wlan[*].agent.typename = "";
31+
}
32+
interferer: WirelessHost {
33+
@display("p=400,350;r=,,#707070");
34+
wlan[*].mgmt.typename = "Ieee80211MgmtStaSimplified";
35+
wlan[*].agent.typename = "";
36+
}
37+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
[General]
2+
network = ChannelWidthsNetwork
3+
sim-time-limit = 2s
4+
5+
# AP and STA configuration
6+
**.ap.wlan[*].address = "10:00:00:00:00:01"
7+
**.sta1.wlan[*].mgmt.accessPointAddress = "10:00:00:00:00:01"
8+
**.sta2.wlan[*].mgmt.accessPointAddress = "10:00:00:00:00:01"
9+
**.interferer.wlan[*].mgmt.accessPointAddress = "10:00:00:00:00:01"
10+
11+
# Radio medium parameters
12+
**.radioMedium.backgroundNoise.power = -110dBm
13+
**.radioMedium.analogModel.ignorePartialInterference = true
14+
15+
# Wi-Fi physical parameters
16+
**.wlan[*].opMode = "n(mixed-2.4Ghz)"
17+
**.wlan[*].radio.transmitter.power = 20mW
18+
**.wlan[*].radio.receiver.sensitivity = -85dBm
19+
**.wlan[*].radio.receiver.energyDetection = -85dBm
20+
**.wlan[*].radio.receiver.snirThreshold = 4dB
21+
22+
# Application: UDP traffic between STA1 and STA2
23+
*.sta1.numApps = 1
24+
*.sta1.app[0].typename = "UdpBasicApp"
25+
*.sta1.app[0].destAddresses = "sta2"
26+
*.sta1.app[0].destPort = 5000
27+
*.sta1.app[0].messageLength = 1000B
28+
*.sta1.app[0].sendInterval = 1ms
29+
*.sta1.app[0].startTime = 0.1s
30+
*.sta1.app[0].stopTime = 1.9s
31+
32+
*.sta2.numApps = 1
33+
*.sta2.app[0].typename = "UdpSink"
34+
*.sta2.app[0].localPort = 5000
35+
36+
# Base HT20 config
37+
[Config Ht20MHz]
38+
description = "IEEE 802.11n 20 MHz operation on 2.4 GHz channel 0"
39+
**.wlan[*].radio.bandwidth = 20MHz
40+
**.wlan[*].radio.channelNumber = 0
41+
**.wlan[*].htSecondaryChannelOffset = "none"
42+
43+
# HT40+ (secondary above)
44+
[Config Ht40MHzSecondaryAbove]
45+
description = "IEEE 802.11n 40 MHz operation with secondary channel above (+20 MHz)"
46+
**.wlan[*].radio.bandwidth = 40MHz
47+
**.wlan[*].radio.channelNumber = 0
48+
**.wlan[*].htSecondaryChannelOffset = "above"
49+
50+
# HT40- (secondary below)
51+
[Config Ht40MHzSecondaryBelow]
52+
description = "IEEE 802.11n 40 MHz operation with secondary channel below (-20 MHz)"
53+
**.wlan[*].radio.bandwidth = 40MHz
54+
**.wlan[*].radio.channelNumber = 4
55+
**.wlan[*].htSecondaryChannelOffset = "below"
56+
57+
# HT40+ with interferer on secondary channel
58+
[Config Ht40MHzSecondaryAboveWithInterferer]
59+
description = "HT40+ with secondary channel interferer causing EDCA backoff restart"
60+
extends = Ht40MHzSecondaryAbove
61+
*.interferer.numApps = 1
62+
*.interferer.app[0].typename = "UdpBasicApp"
63+
*.interferer.app[0].destAddresses = "sta2"
64+
*.interferer.app[0].destPort = 6000
65+
*.interferer.app[0].messageLength = 500B
66+
*.interferer.app[0].sendInterval = 2ms
67+
*.interferer.app[0].startTime = 0.2s
68+
*.interferer.app[0].stopTime = 1.8s
69+
*.interferer.wlan[*].radio.bandwidth = 20MHz
70+
*.interferer.wlan[*].radio.channelNumber = 4
71+
*.interferer.wlan[*].htSecondaryChannelOffset = "none"

src/inet/linklayer/ieee80211/Ieee80211Interface.ned

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,16 @@ module Ieee80211Interface extends NetworkInterface like IWirelessInterface
6464
string interfaceTableModule;
6565
string energySourceModule = default("");
6666
string opMode @enum("a","b","g(erp)","g(mixed)","n(mixed-2.4Ghz)","p","ac") = default("g(mixed)");
67+
string htSecondaryChannelOffset @enum("none", "above", "below") = default("none");
6768
string address @mutable = default("auto"); // MAC address as hex string (12 hex digits), or
6869
// "auto". "auto" values will be replaced by
6970
// a generated MAC address in init stage 0.
7071
string protocol = default("");
7172
double bitrate @unit(bps) = default(-1bps);
7273
**.opMode = this.opMode;
7374
**.bitrate = this.bitrate;
75+
radio.htSecondaryChannelOffset = default(this.htSecondaryChannelOffset);
76+
mib.htSecondaryChannelOffset = default(this.htSecondaryChannelOffset);
7477
mac.modeSet = default(this.opMode);
7578
mac.*.rateSelection.dataFrameBitrate = default(this.bitrate);
7679
*.macModule = default(absPath(".mac"));

src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "inet/linklayer/ieee80211/mac/contract/IRx.h"
2626
#include "inet/linklayer/ieee80211/mac/contract/ITx.h"
2727
#include "inet/networklayer/contract/IInterfaceTable.h"
28+
#include "inet/physicallayer/wireless/ieee80211/contract/IIeee80211CcaProvider.h"
2829
#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211ControlInfo_m.h"
2930
#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h"
3031

@@ -66,11 +67,14 @@ void Ieee80211Mac::initialize(int stage)
6667
ds = check_and_cast<IDs *>(getSubmodule("ds"));
6768
rx = check_and_cast<IRx *>(getSubmodule("rx"));
6869
tx = check_and_cast<ITx *>(getSubmodule("tx"));
70+
auto ccaProvider = dynamic_cast<IIeee80211CcaProvider *>(radio.get());
71+
if (ccaProvider != nullptr) {
72+
radioModule->subscribe(IIeee80211CcaProvider::ccaStateChangedSignal, this);
73+
rx->ccaStateChanged(ccaProvider->getCcaSnapshot());
74+
}
6975
emit(modesetChangedSignal, modeSet);
7076
if (isUp())
7177
initializeRadioMode();
72-
rx = check_and_cast<IRx *>(getSubmodule("rx"));
73-
tx = check_and_cast<ITx *>(getSubmodule("tx"));
7478
dcf = check_and_cast<Dcf *>(getSubmodule("dcf"));
7579
hcf = check_and_cast_nullable<Hcf *>(getSubmodule("hcf"));
7680
if (mib->qos && !hcf)
@@ -327,6 +331,13 @@ void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, intva
327331
}
328332
}
329333

334+
void Ieee80211Mac::receiveSignal(cComponent *source, simsignal_t signalID, cObject *value, cObject *details)
335+
{
336+
Enter_Method("%s", cComponent::getSignalName(signalID));
337+
if (signalID == IIeee80211CcaProvider::ccaStateChangedSignal)
338+
rx->ccaStateChanged(*check_and_cast<Ieee80211CcaSnapshot *>(value));
339+
}
340+
330341
void Ieee80211Mac::configureRadioMode(IRadio::RadioMode radioMode)
331342
{
332343
if (radio->getRadioMode() != radioMode) {

src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class INET_API Ieee80211Mac : public MacProtocolBase
6464
virtual void initializeRadioMode();
6565

6666
virtual void receiveSignal(cComponent *source, simsignal_t signalID, intval_t value, cObject *details) override;
67+
virtual void receiveSignal(cComponent *source, simsignal_t signalID, cObject *value, cObject *details) override;
6768
using MacProtocolBase::receiveSignal;
6869
virtual void configureRadioMode(physicallayer::IRadio::RadioMode radioMode);
6970
virtual void configureNetworkInterface() override;

src/inet/linklayer/ieee80211/mac/Rx.cc

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,37 @@ void Rx::recomputeMediumFree()
159159
{
160160
bool oldMediumFree = mediumFree;
161161
// note: the duration of mode switching (rx-to-tx or tx-to-rx) should also count as busy
162-
mediumFree = receptionState == IRadio::RECEPTION_STATE_IDLE && transmissionState == IRadio::TRANSMISSION_STATE_UNDEFINED && !endNavTimer->isScheduled();
162+
bool primaryPhysicallyIdle = ht40Cca ? !primaryCcaBusy : receptionState == IRadio::RECEPTION_STATE_IDLE;
163+
mediumFree = primaryPhysicallyIdle && transmissionState == IRadio::TRANSMISSION_STATE_UNDEFINED && !endNavTimer->isScheduled();
163164
if (mediumFree != oldMediumFree) {
164165
for (auto contention : contentions)
165166
contention->mediumStateChanged(mediumFree);
166167
}
167168
}
168169

170+
bool Rx::isSecondaryChannelIdleFor(simtime_t interval) const
171+
{
172+
return !ht40Cca || (!secondaryCcaBusy && secondaryCcaIdleSince >= SIMTIME_ZERO &&
173+
simTime() - secondaryCcaIdleSince >= interval);
174+
}
175+
176+
void Rx::ccaStateChanged(const Ieee80211CcaSnapshot& snapshot)
177+
{
178+
Enter_Method("ccaStateChanged");
179+
bool wasHt40Cca = ht40Cca;
180+
bool wasSecondaryBusy = secondaryCcaBusy;
181+
ht40Cca = snapshot.isHt40();
182+
primaryCcaBusy = snapshot.isPrimaryBusy();
183+
secondaryCcaBusy = snapshot.isSecondaryBusy();
184+
if (!ht40Cca)
185+
secondaryCcaIdleSince = -1;
186+
else if (!wasHt40Cca || (wasSecondaryBusy && !secondaryCcaBusy))
187+
secondaryCcaIdleSince = simTime();
188+
else if (secondaryCcaBusy)
189+
secondaryCcaIdleSince = -1;
190+
recomputeMediumFree();
191+
}
192+
169193
void Rx::receptionStateChanged(IRadio::ReceptionState state)
170194
{
171195
Enter_Method("receptionStateChanged");

src/inet/linklayer/ieee80211/mac/Rx.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ class INET_API Rx : public SimpleModule, public IRx
3737
physicallayer::IRadio::TransmissionState transmissionState = physicallayer::IRadio::TRANSMISSION_STATE_UNDEFINED;
3838
physicallayer::IRadioSignal::SignalPart receivedPart = physicallayer::IRadioSignal::SIGNAL_PART_NONE;
3939
bool mediumFree = true; // cached state
40+
bool ht40Cca = false;
41+
bool primaryCcaBusy = false;
42+
bool secondaryCcaBusy = false;
43+
simtime_t secondaryCcaIdleSince = -1;
4044

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

5458
virtual bool isReceptionInProgress() const override;
5559
virtual bool isMediumFree() const override { return mediumFree; }
60+
virtual bool isSecondaryChannelIdleFor(simtime_t interval) const override;
5661
virtual void receptionStateChanged(physicallayer::IRadio::ReceptionState newReceptionState) override;
62+
virtual void ccaStateChanged(const physicallayer::Ieee80211CcaSnapshot& snapshot) override;
5763
virtual void transmissionStateChanged(physicallayer::IRadio::TransmissionState transmissionState) override;
5864
virtual void receivedSignalPartChanged(physicallayer::IRadioSignal::SignalPart part) override;
5965
virtual bool lowerFrameReceived(Packet *packet) override;

src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,17 @@ void Dcaf::releaseChannel(IChannelAccess::ICallback *callback)
105105
EV_INFO << "Channel released.\n";
106106
}
107107

108+
void Dcaf::restartChannelAccess(IChannelAccess::ICallback *callback)
109+
{
110+
Enter_Method("restartChannelAccess");
111+
ASSERT(owning && this->callback == callback);
112+
owning = false;
113+
emit(channelOwnershipChangedSignal, owning);
114+
// IEEE Std 802.11-2024, 11.15.9 item b): retain the current CW and draw a
115+
// fresh random backoff without updating any retry counter.
116+
contention->startContention(cw, ifs, eifs, slotTime, this);
117+
}
118+
108119
void Dcaf::requestChannel(IChannelAccess::ICallback *callback)
109120
{
110121
Enter_Method("requestChannel");

src/inet/linklayer/ieee80211/mac/channelaccess/Dcaf.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class INET_API Dcaf : public IChannelAccess, public IContention::ICallback, publ
5252
// IChannelAccess::ICallback
5353
virtual void requestChannel(IChannelAccess::ICallback *callback) override;
5454
virtual void releaseChannel(IChannelAccess::ICallback *callback) override;
55+
virtual void restartChannelAccess(IChannelAccess::ICallback *callback);
5556

5657
// IContention::ICallback
5758
virtual void channelAccessGranted() override;

src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ void Edcaf::releaseChannel(IChannelAccess::ICallback *callback)
143143
EV_INFO << "Channel released.\n";
144144
}
145145

146+
void Edcaf::restartChannelAccess(IChannelAccess::ICallback *callback)
147+
{
148+
Enter_Method("restartChannelAccess");
149+
ASSERT(owning);
150+
owning = false;
151+
emit(channelOwnershipChangedSignal, owning);
152+
// IEEE Std 802.11-2024, 11.15.9 item b): retain CW[AC], select a new
153+
// random backoff, and leave retry counters and queued frames unchanged.
154+
contention->startContention(cw, ifs, eifs, slotTime, this);
155+
}
156+
146157
void Edcaf::requestChannel(IChannelAccess::ICallback *callback)
147158
{
148159
Enter_Method("requestChannel");

0 commit comments

Comments
 (0)