Skip to content

Commit 8b38d46

Browse files
committed
[#1323] downlinkHandling: refine module docs and add release note snippet
1 parent 87a38fa commit 8b38d46

2 files changed

Lines changed: 69 additions & 14 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- Added :ref:`downlinkHandling` with a validated configuration interface (setters/getters), finite-value guards, and bounded outputs to prevent non-physical downlink rates.
2+
- Added :ref:`DownlinkHandlingMsgPayload` diagnostics and dedicated unit-test coverage for equation parity, receiver-path selection, storage-limited behavior, and invalid-input handling.
3+
- Improved storage-target selection robustness across connected storage status messages and aligned module documentation with implemented behavior and validation interface.

src/simulation/communication/downlinkHandling/downlinkHandling.rst

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ At each simulation step, the module:
1313
- reads link quality from :ref:`linkBudget`
1414
- converts CNR into BER and packet error rate
1515
- applies retry-limited ARQ reliability
16-
- removes data from onboard storage at the resulting effective rate
16+
- removes data from onboard storage at the resulting effective rate when storage routing is safely actionable
1717
- publishes detailed diagnostics for analysis and fault studies
1818

1919
The module is designed to sit between RF-link modeling (:ref:`simpleAntenna`, :ref:`linkBudget`)
@@ -50,7 +50,7 @@ The message connection is set by the user from Python.
5050
* - ``storageUnitInMsgs`` (via ``addStorageUnitToDownlink``)
5151
- :ref:`DataStorageStatusMsgPayload`
5252
- Storage state input (partition names, partition bits, total storage level).
53-
- Required for actual data removal
53+
- Required for actual data removal; actual removal is currently limited to a single linked storage unit
5454
* - ``nodeDataOutMsg``
5555
- :ref:`DataNodeUsageMsgPayload`
5656
- Data-node output inherited from ``DataNodeBase``. Negative baud rate removes bits from storage.
@@ -67,7 +67,7 @@ The module extends ``DataNodeBase`` and runs once per simulation step.
6767
The per-step sequence is:
6868

6969
1. Read ``LinkBudgetMsgPayload`` and all connected storage status messages.
70-
2. Select one storage partition (largest currently available data in the most recently connected storage unit).
70+
2. Select one candidate storage target (largest finite partition across all connected storage status messages; use ``storageLevel`` only when a message has no partition vector).
7171
3. Select receiver path (forced receiver index or auto mode).
7272
4. Convert selected CNR and overlap bandwidth into :math:`C/N_0`.
7373
5. Convert :math:`C/N_0` and requested bit rate into :math:`E_b/N_0`.
@@ -77,6 +77,11 @@ The per-step sequence is:
7777
9. Apply packet gating and storage saturation.
7878
10. Write ``nodeDataOutMsg`` and ``downlinkOutMsg``.
7979

80+
The candidate-selection logic can inspect multiple storage status messages, but actual storage removal is intentionally conservative:
81+
``nodeDataOutMsg`` carries only ``dataName`` and ``baudRate``, so it cannot safely target one specific storage unit when multiple
82+
storage units are linked. In that case, downlinkHandling reports the selected candidate in diagnostics but forces removal to zero
83+
for that step to avoid silent cross-unit corruption.
84+
8085
Configurable Parameters
8186
^^^^^^^^^^^^^^^^^^^^^^^
8287

@@ -104,11 +109,31 @@ Configurable Parameters
104109
- 0
105110
- -
106111
- Receiver selection: 0=auto, 1=use receiver path 1, 2=use receiver path 2.
112+
* - ``removalPolicy``
113+
- ``REMOVE_ATTEMPTED`` (0)
114+
- -
115+
- Storage removal mode: ``REMOVE_ATTEMPTED`` removes delivered+drop-limited bits, ``REMOVE_DELIVERED_ONLY`` removes only successfully delivered bits.
107116
* - ``requireFullPacket``
108117
- ``True``
109118
- bool
110119
- If ``True``, downlink waits until selected storage has at least one full packet.
111120

121+
Configuration Interface and Validation
122+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
123+
124+
The module exposes validated setters in C++/Python:
125+
126+
- ``setBitRateRequest(bitRateRequest)`` with :math:`bitRateRequest \ge 0`
127+
- ``setPacketSizeBits(packetSizeBits)`` with :math:`packetSizeBits > 0`
128+
- ``setMaxRetransmissions(maxRetransmissions)`` with :math:`maxRetransmissions \ge 1`
129+
- ``setReceiverAntenna(receiverAntenna)`` with ``receiverAntenna in {0,1,2}``
130+
- ``setRemovalPolicy(removalPolicy)`` with ``removalPolicy in {0,1}``
131+
- ``setRequireFullPacket(requireFullPacket)``
132+
133+
If a setter receives an invalid value, the module rejects it and keeps the last valid value.
134+
The Python wrapper also maps ``bitRateRequest``, ``packetSizeBits``, ``maxRetransmissions``,
135+
``receiverAntenna``, ``removalPolicy``, and ``requireFullPacket`` to these validated setter/getter paths.
136+
112137
Receiver Selection and CNR1/CNR2 Usage
113138
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114139
The module uses receiver-specific fields from :ref:`LinkBudgetMsgPayload`:
@@ -222,11 +247,21 @@ Final rates:
222247
.. math::
223248
224249
R_{\mathrm{attempt}} = s\,R_{\mathrm{attempt,pot}}, \quad
225-
R_{\mathrm{remove}} = s\,R_{\mathrm{remove,pot}}, \quad
250+
R_{\mathrm{remove,modeled}} = s\,R_{\mathrm{remove,pot}}, \quad
226251
R_{\mathrm{delivered}} = s\,R_{\mathrm{delivered,pot}}, \quad
227252
R_{\mathrm{dropped}} = s\,R_{\mathrm{dropped,pot}}
228253
229-
The value written to storage through ``nodeDataOutMsg`` is:
254+
Actual storage removal follows ``removalPolicy``:
255+
256+
.. math::
257+
258+
R_{\mathrm{remove}} =
259+
\begin{cases}
260+
R_{\mathrm{remove,modeled}}, & \text{REMOVE\_ATTEMPTED} \\
261+
R_{\mathrm{delivered}}, & \text{REMOVE\_DELIVERED\_ONLY}
262+
\end{cases}
263+
264+
The value written to storage through ``nodeDataOutMsg`` is then:
230265

231266
.. math::
232267
@@ -236,7 +271,7 @@ Output Diagnostics
236271
------------------
237272
The custom output :ref:`DownlinkHandlingMsgPayload` reports:
238273

239-
- link/selection state (``linkActive``, ``receiverIndex``, antenna names)
274+
- link/selection state (``linkActive``, ``receiverIndex``, antenna names, ``removalPolicy``)
240275
- physical-layer quality terms (CNR, :math:`C/N_0`, :math:`E_b/N_0`, BER, PER)
241276
- ARQ reliability terms (success/drop probabilities, expected attempts)
242277
- rate terms (attempted, removed, delivered, dropped)
@@ -252,6 +287,12 @@ Typical chain:
252287
3. :ref:`downlinkHandling` converts link quality to effective data transfer and storage removal.
253288
4. Storage modules consume ``nodeDataOutMsg`` and reduce onboard buffered data.
254289

290+
.. warning::
291+
292+
Also important integration note:
293+
Do not run ``spaceToGroundTransmitter`` and ``downlinkHandling`` as competing downlink removers
294+
on the same storage partitions. Pick one downlink path.
295+
255296
This separation is useful for fault modeling: upstream RF degradation (pointing, frequency mismatch,
256297
atmospheric attenuation, receive-state changes) naturally propagates into BER/PER and delivered data.
257298

@@ -263,7 +304,10 @@ Assumptions and Current Limits
263304
- Any bit error fails the packet.
264305
- ARQ is expectation-based, not packet-by-packet Monte Carlo.
265306
- No explicit ACK latency, coding gain, framing overhead, or adaptive coding/modulation.
266-
- Storage partition selection currently targets the largest partition in the latest connected storage unit.
307+
- ``REMOVE_DELIVERED_ONLY`` preserves dropped/undelivered bits onboard, but the module still uses an expected-rate ARQ model instead of explicit packet ACK/NACK timelines.
308+
- Storage target selection prioritizes per-partition values. ``storageLevel`` is only used as fallback for messages that do not provide ``storedData`` entries.
309+
- ``nodeDataOutMsg`` identifies storage by ``dataName`` only and cannot address a specific storage unit. Accordingly, downlinkHandling forces removal to zero whenever more than one storage unit is linked through ``addStorageUnitToDownlink``. Multi-storage status inspection is still supported for diagnostics and candidate selection, but actual removal currently requires a single linked storage unit.
310+
- If a selected storage status message does not provide an explicit partition name, downlinkHandling also forces removal to zero for that step rather than emitting an aggregate negative rate that downstream storage cannot route safely.
267311

268312
Unit Test Coverage
269313
------------------
@@ -276,22 +320,30 @@ The tests verify:
276320
- equation parity versus a Python-equivalent BER/PER/ARQ model
277321
- zero-flow behavior for invalid link inputs
278322
- retry-cap effects on drop probability and removal/delivery behavior
323+
- removal-policy behavior (``REMOVE_ATTEMPTED`` vs ``REMOVE_DELIVERED_ONLY``)
279324
- storage-limited rate capping and drain behavior
280325
- automatic receiver selection from antenna RX states and CNR values
326+
- duplicate-storage input rejection
327+
- storage-target selection across multiple storage status messages
328+
- conservative removal blocking when more than one storage unit is linked
329+
- ambiguous duplicate partition-name behavior across multiple storage status messages
330+
331+
User Guide
332+
----------
281333

282-
Usage Snippet
283-
-------------
334+
Basic setup example:
284335

285336
.. code-block:: python
286337
287338
from Basilisk.simulation import downlinkHandling, simpleStorageUnit
288339
289340
dlh = downlinkHandling.DownlinkHandling()
290-
dlh.bitRateRequest = 1.0e5 # bit/s
291-
dlh.packetSizeBits = 1024.0 # bit
292-
dlh.maxRetransmissions = 8
293-
dlh.receiverAntenna = 0 # auto-select valid RX path with highest CNR
294-
dlh.requireFullPacket = True
341+
dlh.setBitRateRequest(1.0e5) # [bit/s]
342+
dlh.setPacketSizeBits(1024.0) # [bit]
343+
dlh.setMaxRetransmissions(8) # [-]
344+
dlh.setReceiverAntenna(0) # [-] auto-select valid RX path with highest CNR
345+
dlh.setRemovalPolicy(0) # [-] 0=REMOVE_ATTEMPTED, 1=REMOVE_DELIVERED_ONLY
346+
dlh.setRequireFullPacket(True) # [-]
295347
296348
storage = simpleStorageUnit.SimpleStorageUnit()
297349
storage.storageCapacity = int(8e9)

0 commit comments

Comments
 (0)