Skip to content

physicallayer: opt-in radio and medium extensions for cellular PHY models - #1281

Open
torokati44 wants to merge 7 commits into
masterfrom
topic/radio-medium-cellular-enablers
Open

torokati44 wants to merge 7 commits into
masterfrom
topic/radio-medium-cellular-enablers

Conversation

@torokati44

@torokati44 torokati44 commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

This PR adds features to the packet-level wireless layer that a cellular physical layer (Simu5G) needs. Today Simu5G works around their absence by subclassing or copying INET code. Every feature is off by default, and with the defaults existing simulations behave exactly as before (see Verification).

Commit What it does Setting (default)
Radio: concurrent transmissions and receptions transmissionTimer and receptionTimer become vectors, so one radio (e.g. a base station) can transmit, and attempt to receive, several signals at once allowConcurrentTransmissions, allowConcurrentReceptions (false)
IArrival, IPropagation: the arrival knows its receiver radio New IArrival::getReceiverRadio(). computeArrival() takes the receiver radio instead of its mobility, so a path loss model can keep state per link — (API change)
LogNormalShadowing: optional per-link correlation distance Keeps one shadowing value per radio pair, the same in both directions, and draws a new one when either radio moves farther than the distance. Values are dropped when a radio is removed. Setting it with DimensionalMediumAnalogModel is an error correlationDistance (NaN = previous behavior)
IRadioMedium: computeInterference() for a listening of the caller's making The protected computeInterference(receiver, listening) becomes the public computeInterference(listening), for what-if SNIR and link-quality estimates — (API change)
RadioMedium: transmissionPurgeMode Removes stale transmissions whenever a new one is added, instead of with a self-message, so the medium adds no events of its own transmissionPurgeMode ("timer")
IRadio: every transmission and reception in progress New getTransmissionsInProgress() and getReceptionsInProgress(). Their default bodies wrap the existing single-result getters, which Radio makes throw when concurrency is enabled. ReceiverBase and the canvas visualizers use the new getters —
Radio: log the attempted receptions that are dropped abandonAttemptedReceptions() replaces the silent receptionTimer = nullptr in the reconfiguration setters —

C++ API changes (WHATSNEW incompatible items 19–21) affect:

  • Radio subclasses that use receptionTimer/transmissionTimer or override continueTransmission(), endTransmission() or abortTransmission();
  • code that implements or calls IPropagation::computeArrival(), or constructs Arrival objects;
  • IRadioMedium implementations that don't derive from RadioMedium.

The only other INET code that needed updating is in the touched files: ReceiverBase, the canvas visualizers, L3NetworkConfiguratorBase and the reconfiguration setters of the Radio subclasses.

Points worth a reviewer's attention

  • With concurrency enabled, receivedSignalPart, transmittedSignalPart and the part-changed signals each follow a single signal: the first attempted reception, or the transmission whose timer comes first. This is documented on the members. Tracking every signal would need a contract change.
  • computeInterference(listening) computes and caches any receptions not yet computed. A random path loss model therefore draws its random numbers earlier than it otherwise would. The contract says so.
  • LogNormalShadowing subscribes to the medium's radioRemovedSignal only when a correlation distance is set.

Verification (release build, OMNeT++ 6.4, base master 93210f4):

  • Fingerprints: wireless-combo, examples, showcases, tutorials, mipv6-refactoring (1099 rows, -F tyf). After every commit, the same 2 rows fail with the same values as on master, plus the same 14 missing-feature errors.
  • Module tests: after every commit, the same failures as on master, which are 34 tcp_* tests that already fail there. 14 new tests pass: ConcurrentRadio_1..5, LogNormalShadowing_Correlated_1..5, RadioMedium_ComputeInterference, RadioMedium_PurgeByTimer, RadioMedium_PurgeOnTransmission and Radio_AbandonedReception.
  • Purge mode on: wireless-combo with transmissionPurgeMode="onTransmission" (measured before the rebase): only the event-count ingredient (tplx) changes, because the medium's timer events disappear. The other two ingredients (~tNl, ~tND) are unchanged in all rows.

Devin Review

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

updateTransceiverPart();
if (timer == receptionTimer)
receptionTimer = nullptr;
remove(attemptedReceptionTimers, timer);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Received signal part follows finished reception

When the oldest concurrent reception ends, receivedSignalPart still follows its timer while another reception continues. attemptedReceptionTimers loses that timer only after the part update, so energy consumers can use the wrong reception stage.

Learn more

The radio tracks attempted receptions in start order. updateTransceiverPart() reads the first entry to publish the active signal part, and the energy consumer uses that part when calculating reception power. When the first of two concurrent receptions ends, its timer is no longer scheduled but remains first in the vector during the part update. Removing it afterward does not publish the surviving reception's part until a later radio event.

Example: Reception A starts before reception B. A finishes during B's header. The radio continues reporting A's final part rather than B's header until another update occurs.

Recommended fix: Remove the finished timer from attemptedReceptionTimers before updating the part in Radio::endReception, while preserving the existing packet-delivery and signal ordering where required. Cover overlapping receptions with different parts and end times.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@levy levy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTheThing -> returns the one thing if there's only one thing and not more things or nullptr if there's none

No transmission/reception process or state needs to get special treatment anywhere in the code. We can further extend the API if needed, but we should not invent weird meanings to function which no caller would ever need and use.

* transmitting or nullptr.
* Returns an ongoing transmission that the transmitter is currently
* transmitting or nullptr. A radio that transmits concurrently returns
* one of its transmissions in progress.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should throw an exception when there are multiple transmissions going on. Why would returning a random transmission useful? Same goes for receptions.

return timer;
}

cMessage *Radio::getFirstScheduledTransmissionTimer() const

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getFirstScheduledTransmissionTimer() is wrong just by looking at its name. Why would the first be special? If there's exactly zero or one then it could return nullptr or the one, but if there's more than one then why would the first one need special treatment?

* The current transmitted signal part. With several transmissions in
* progress, the part of the one whose timer comes first in
* transmissionTimers.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What? This state randomly jumps between different transmissions based on which timer comes first? What would it be good for?

The AI is really messing this concurrent transmission/reception up.

@torokati44
torokati44 force-pushed the topic/radio-medium-cellular-enablers branch from cd9b1ab to c2351fc Compare October 6, 2026 09:45
Comment thread src/inet/physicallayer/wireless/common/contract/packetlevel/IRadioMedium.h Outdated
@torokati44
torokati44 force-pushed the topic/radio-medium-cellular-enablers branch from c2351fc to d9d0eac Compare October 6, 2026 10:11
IRadio answers for one transmission and one attempted reception in
progress, getTransmissionInProgress() and getReceptionInProgress(). Code
that asks whether a given signal is the one in progress, such as
ReceiverBase deciding whether to attempt a reception and the canvas
visualizers linking a figure to a signal, cannot work with a radio that
has several in progress at once, which a later commit of this series
("Radio: concurrent transmissions and receptions") lets Radio have.

IRadio gains getTransmissionsInProgress() and getReceptionsInProgress(),
which return every one in progress. Radio, NoiseSource and ShortcutRadio,
which have at most one in each direction, implement them from their
singular getters. The callers switch to them: ReceiverBase attempts a
reception when nothing is being attempted or the reception is among the
attempted ones, and not when a preceding interferer is among them.
RadioCanvasVisualizer links the reception or transmission state figure to
a signal only when it is the only one in progress in that direction.
MediumCanvasVisualizer shows a signal in its spectrum figures only when
neither direction has several in progress; otherwise they show the
medium, as with none. With at most one in progress every answer is the
same as before.

The two methods are pure virtual rather than defaulted to the singular
getters: a radio that has several signals in progress and inherited such
a default would silently report one of them. C++ API change, stated in
WHATSNEW: an IRadio implementation must implement both.

Test: Radio_InProgressGetters (halfway through the only signal, the
plural getters hold exactly the transmission and the reception that the
singular ones answer; after it, nothing).
A reconfiguration of the radio (bitrate, bandwidth, and the IEEE 802.11
mode set, mode, band and channel) stops attempting the reception in
progress by resetting the receptionTimer member, and a newly attempted
reception replaces the one in progress the same way. Neither is logged:
the log shows a reception "attempting" and later "ignoring", with nothing
between to say why.

The new abandonAttemptedReceptions() does this for the seven setters and
startReception(), and logs "Reception abandoned" for the reception, in
the format of the other reception lines. The timer stays scheduled, owned
by allReceptionTimers, and the reception is ignored when it ends, as
before; the reception state and the received signal part are still
updated only by the next reception event. The call also gives the
subclasses one place that drops the attempt, which the next commit needs
when the single member becomes a vector.

Test: Radio_AbandonedReception (the receiving radio's bandwidth is set
halfway through the only signal: attempting, abandoned, ignoring, nothing
sent up).
Radio held one transmission timer and one attempted-reception timer, so it
could transmit one signal and attempt one reception at a time. Both become
vectors: transmissionTimers (a pool: idle timers are reused, so a radio that
never transmits concurrently still allocates exactly one) and
attemptedReceptionTimers.

Two new parameters, both default(false), keep today's behavior:
- allowConcurrentTransmissions: without it, a packet from above while
  transmitting is still an error;
- allowConcurrentReceptions: without it, a newly attempted reception replaces
  the one in progress, as the single member did. The receivers shipped with
  INET attempt one reception at a time, except Ieee802154UwbIrReceiver,
  which attempts every one and relies on that replacement.

Radio's getTransmissionsInProgress() and getReceptionsInProgress() return
every transmission and attempted reception in progress. Its
getTransmissionInProgress(), getReceptionInProgress(),
getTransmittedSignalPart() and getReceivedSignalPart() answer for the one
signal in progress in their direction, nullptr or SIGNAL_PART_NONE when
there is none, and throw an error when there are several: then there is no
one transmission, reception or signal part. The two signal parts, and the
signals emitted when they change, are only updated while at most one is in
progress; when one of two attempted receptions ends, the received part
becomes the other one's. A model that relies on the parts, such as the
StateBasedEpEnergyConsumer and StateBasedCcEnergyConsumer energy consumers,
therefore cannot be used with a radio that has several signals in progress
at once. The callers in INET that must see every signal already ask for
all of them, and abandonAttemptedReceptions() now drops every attempted
reception.

C++ API change for Radio subclasses: receptionTimer/transmissionTimer are
gone, continueTransmission(), endTransmission() and abortTransmission() take
the timer, and a subclass that dropped the attempted reception by resetting
receptionTimer calls abandonAttemptedReceptions().

Tests: ConcurrentRadio_1 (both enabled: two overlapping transmissions from
one radio, both attempted and sent up at the receiver; halfway through, 2
transmissions and 2 receptions in progress, and the singular and part
getters throw), _2 (the default transmission guard), _3 (the default
replacement), _4 (on a dimensional medium, three senders at once, two
sharing a band and one on another, all attempted by a receiver that takes
any signal within its listening band: the pair collides, the third is
decoded; 3 receptions in progress), _5 (with nothing in progress the
singular getters answer nullptr and the part getters NONE, with and without
concurrency) and _6 (separate reception parts: with two attempted receptions
the received part getter throws; when the earlier one ends, the part is the
header of the other one).
An arrival was always computed for one receiver, but did not record which:
IPropagation::computeArrival() took the receiver's mobility only, so an
IPathLoss saw the transmitter (through the transmission) but not the
receiver, and no path loss could keep per-link state.

IArrival::getReceiverRadio() returns the radio the arrival was computed
for; Arrival takes it as its first constructor argument.
computeArrival() takes the receiver radio instead of its mobility, and the
two implementations read the mobility from the radio's antenna. The four
callers (RadioMedium twice, L3NetworkConfiguratorBase twice) already had
the radio at hand. API change, stated in WHATSNEW.
LogNormalShadowing drew a new shadowing value on every path loss
computation: two receptions of the same link at the same position could
differ by several sigma. With the arrival now knowing its receiver radio,
a path loss can keep per-link state; this is the first to do so.

New parameter correlationDistance, default NaN = the memoryless draw as
before. With a distance, the value is kept per pair of radios, keyed by
the smaller radio id first, together with the position of each radio at
the draw, and drawn again when either radio has moved farther than the
distance from its position then. Shadowing is caused by the obstacles on
the path between the two radios: it is the same in both directions, and
it changes when either end moves (with a receiver-only rule, the uplink
of a node moving away from a stationary base station would keep its first
value forever). The shadowing draw moved into a helper overload; in the
default mode it is drawn once per computation at the same point as before.

The values of a radio's links are dropped when the radio is removed from
the medium, so that in a simulation where nodes come and go the map does
not grow with every radio that ever existed. When a correlation distance
is set, the module subscribes to IRadioMedium::radioRemovedSignal at its
parent module, the medium, which emits the signal on itself; the parent
is checked to be an IRadioMedium, as the obstacle loss models and
MediumLimitCache look up theirs. With the default nothing is subscribed.

The correlation needs a medium analog model that computes the path loss
per link, through computePathLoss(transmission, arrival), as
ScalarMediumAnalogModel does. DimensionalMediumAnalogModel computes it per
frequency from the distance alone, where the correlation would silently
have no effect, so a correlation distance with it is an error at
initialization.

Tests: LogNormalShadowing_Correlated_1..5 (stationary nodes: one power for
all eight receptions, both directions; the default: a new value every
time; host2 circling a stationary host1 at constant distance: a new value
per ping, reused by its reply, four powers each received in both
directions; host2 deleted after two pings: its one pair is dropped; a
correlation distance on a dimensional medium: the error).
…aking

RadioMedium's public getInterference() only works for transmissions added
through transmitPacket(): it is a cache read. A receiver that wants the
interference over an interval it is not receiving in -- a link-quality
estimate, a what-if SNIR -- had to subclass the medium to reach the
protected computeInterference(receiver, listening).

That method moves, unchanged, to the IRadioMedium contract and becomes
public in RadioMedium: computeInterference(receiver, listening) is the
background noise plus the receptions, at the receiver, of the transmissions
on the medium that overlap the listening in time and are in interference
range, except the receiver's own. The result is not cached, but the
receptions in it come from the medium's reception cache: the ones not
computed yet are computed at the call, and draw their random numbers, if
any, then, earlier than the simulation would otherwise do; the contract
says so. The caller owns the result (deleting it deletes the noise and the
reception vector, not the receptions, which the medium owns). No
convenience wrapper: the propagation, the receiver's createListening() and
the analog model are public already, so a what-if SNIR composes from
primitives.

Test: RadioMedium_ComputeInterference (during a ping, the interference at
the receiver over an ad-hoc listening contains exactly the sender's
reception; at the sender, nothing; afterwards, nothing).
Transmissions that can no longer interfere are removed from the
communication cache by a self-message of the medium, scheduled at the
interference end time of the oldest transmission that can still
interfere; each firing also walks the cache to reschedule it. The timer is
an event in every simulation with a radio medium, which a model whose
fingerprints must not depend on the medium's bookkeeping cannot
compensate for.

New parameter transmissionPurgeMode: "timer" (default, as before) or
"onTransmission", which purges at the end of addTransmission() instead of
scheduling the timer. The purge comes after the new transmission's
interference end time is set: until then its cache entry's end time is 0,
and a purge would remove it. Interference is unaffected: a transmission
whose interference end time has passed overlaps nothing that can still be
received.

removeNonInterferingTransmissions() now only removes, as its name says,
and both modes call it. The rescheduling walk it used to end with is the
new scheduleRemoveNonInterferingTransmissionsTimer(), which the timer
handler calls after it. A subclass that overrides or calls
removeNonInterferingTransmissions() therefore no longer reschedules the
timer through it; none in INET does.

Tests: RadioMedium_PurgeOnTransmission (20 pings: no purge event, all
replies delivered, 2 transmissions left in the cache) and
RadioMedium_PurgeByTimer (the default: purge events, cache emptied).
@torokati44
torokati44 force-pushed the topic/radio-medium-cellular-enablers branch from d9d0eac to 3067a09 Compare October 6, 2026 16:05

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants