Skip to content

Commit e70f55b

Browse files
committed
[#1323] downlinkHandling: refine module docs and add release note snippet
1 parent 3f385a9 commit e70f55b

2 files changed

Lines changed: 73 additions & 18 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: 70 additions & 18 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

@@ -93,22 +98,41 @@ Configurable Parameters
9398
- bit/s
9499
- Requested raw channel bit rate :math:`R_b`. If :math:`R_b \le 0`, throughput is zero.
95100
* - ``packetSizeBits``
96-
- 256.0
101+
- 256
97102
- bit
98103
- Packet length :math:`L` for BER-to-PER conversion.
99104
* - ``maxRetransmissions``
100105
- 10
101-
- -
106+
-
102107
- Retry cap used in the ARQ model. Current implementation enforces :math:`N \ge 1` and treats :math:`N` as maximum transmission attempts.
103108
* - ``receiverAntenna``
104109
- 0
105-
- -
110+
-
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``
109-
- bool
118+
-
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+
Use the explicit setter/getter methods in Python to keep the interface consistent with the C++ API.
135+
112136
Receiver Selection and CNR1/CNR2 Usage
113137
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114138
The module uses receiver-specific fields from :ref:`LinkBudgetMsgPayload`:
@@ -222,11 +246,21 @@ Final rates:
222246
.. math::
223247
224248
R_{\mathrm{attempt}} = s\,R_{\mathrm{attempt,pot}}, \quad
225-
R_{\mathrm{remove}} = s\,R_{\mathrm{remove,pot}}, \quad
249+
R_{\mathrm{remove,modeled}} = s\,R_{\mathrm{remove,pot}}, \quad
226250
R_{\mathrm{delivered}} = s\,R_{\mathrm{delivered,pot}}, \quad
227251
R_{\mathrm{dropped}} = s\,R_{\mathrm{dropped,pot}}
228252
229-
The value written to storage through ``nodeDataOutMsg`` is:
253+
Actual storage removal follows ``removalPolicy``:
254+
255+
.. math::
256+
257+
R_{\mathrm{remove}} =
258+
\begin{cases}
259+
R_{\mathrm{remove,modeled}}, & \text{REMOVE\_ATTEMPTED} \\
260+
R_{\mathrm{delivered}}, & \text{REMOVE\_DELIVERED\_ONLY}
261+
\end{cases}
262+
263+
The value written to storage through ``nodeDataOutMsg`` is then:
230264

231265
.. math::
232266
@@ -236,7 +270,7 @@ Output Diagnostics
236270
------------------
237271
The custom output :ref:`DownlinkHandlingMsgPayload` reports:
238272

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

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

@@ -263,7 +303,10 @@ Assumptions and Current Limits
263303
- Any bit error fails the packet.
264304
- ARQ is expectation-based, not packet-by-packet Monte Carlo.
265305
- 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.
306+
- ``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.
307+
- Storage target selection prioritizes per-partition values. ``storageLevel`` is only used as fallback for messages that do not provide ``storedData`` entries.
308+
- ``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.
309+
- If a selected storage status message does not provide an explicit partition name, downlinkHandling also forces removal to zero for that step and emits an empty ``nodeDataOutMsg.dataName`` rather than emitting an aggregate negative rate that downstream storage cannot route safely.
267310

268311
Unit Test Coverage
269312
------------------
@@ -276,22 +319,31 @@ The tests verify:
276319
- equation parity versus a Python-equivalent BER/PER/ARQ model
277320
- zero-flow behavior for invalid link inputs
278321
- retry-cap effects on drop probability and removal/delivery behavior
322+
- removal-policy behavior (``REMOVE_ATTEMPTED`` vs ``REMOVE_DELIVERED_ONLY``)
279323
- storage-limited rate capping and drain behavior
280324
- automatic receiver selection from antenna RX states and CNR values
325+
- duplicate-storage input rejection
326+
- storage-target selection across multiple storage status messages
327+
- conservative removal blocking when more than one storage unit is linked
328+
- conservative removal blocking when a selected partition has no explicit name
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) # [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)