Repository navigation
physicallayer: opt-in radio and medium extensions for cellular PHY models - #1281
torokati44 wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
Devin Review found 1 potential issue.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
| updateTransceiverPart(); | ||
| if (timer == receptionTimer) | ||
| receptionTimer = nullptr; | ||
| remove(attemptedReceptionTimers, timer); |
There was a problem hiding this comment.
🟡 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
levy
left a comment
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. | ||
| */ |
There was a problem hiding this comment.
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.
cd9b1ab to
c2351fc
Compare
c2351fc to
d9d0eac
Compare
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).
d9d0eac to
3067a09
Compare
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).
transmissionTimerandreceptionTimerbecome vectors, so one radio (e.g. a base station) can transmit, and attempt to receive, several signals at onceallowConcurrentTransmissions,allowConcurrentReceptions(false)IArrival::getReceiverRadio().computeArrival()takes the receiver radio instead of its mobility, so a path loss model can keep state per linkDimensionalMediumAnalogModelis an errorcorrelationDistance(NaN = previous behavior)computeInterference(receiver, listening)becomes the publiccomputeInterference(listening), for what-if SNIR and link-quality estimatestransmissionPurgeMode("timer")getTransmissionsInProgress()andgetReceptionsInProgress(). Their default bodies wrap the existing single-result getters, whichRadiomakes throw when concurrency is enabled.ReceiverBaseand the canvas visualizers use the new gettersabandonAttemptedReceptions()replaces the silentreceptionTimer = nullptrin the reconfiguration settersC++ API changes (WHATSNEW incompatible items 19–21) affect:
Radiosubclasses that usereceptionTimer/transmissionTimeror overridecontinueTransmission(),endTransmission()orabortTransmission();IPropagation::computeArrival(), or constructsArrivalobjects;IRadioMediumimplementations that don't derive fromRadioMedium.The only other INET code that needed updating is in the touched files:
ReceiverBase, the canvas visualizers,L3NetworkConfiguratorBaseand the reconfiguration setters of theRadiosubclasses.Points worth a reviewer's attention
receivedSignalPart,transmittedSignalPartand 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.LogNormalShadowingsubscribes to the medium'sradioRemovedSignalonly when a correlation distance is set.Verification (release build, OMNeT++ 6.4, base
master93210f4):wireless-combo,examples,showcases,tutorials,mipv6-refactoring(1099 rows,-F tyf). After every commit, the same 2 rows fail with the same values as onmaster, plus the same 14 missing-feature errors.master, which are 34tcp_*tests that already fail there. 14 new tests pass:ConcurrentRadio_1..5,LogNormalShadowing_Correlated_1..5,RadioMedium_ComputeInterference,RadioMedium_PurgeByTimer,RadioMedium_PurgeOnTransmissionandRadio_AbandonedReception.wireless-combowithtransmissionPurgeMode="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.