Commit Graph
10 Commits
Author SHA1 Message Date
misadeksandCopilot App 989b9ea831 MS: de-register when entering manual selection or starting a survey while registered
Entering manual cell selection or starting a receive-only survey abandons the serving cell, so a registration bound to that cell (cl. 16.4) must be torn down first. MM now drives a clean U-ITSI DETACH de-registration (cl. 16.6.1) when SetCellSelectionMode(manual) or StartCellScan arrives while Registered. The survey is deferred until the detach drains so it actually reaches the air before the MS tunes away; switching to manual keeps the camp so its detach transmits normally. StopCellScan cancels a deferred survey. Adds 4 regression tests (338 lib tests green).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-26 12:58:51 +02:00
misadeksandCopilot App b41ac931c0 fix(ms): survey advances off carriers that lock an undecodable signal
The survey advanced only on a decodable D-MLE-SYNC (SYNC/SYSINFO finalize) or on the PHY scan-dwell heartbeat, which the PHY emits only while UNSYNCHRONIZED. A candidate that demod-locks a signal the MS cannot decode into a D-MLE-SYNC (non-TETRA emitter, or an undecodable/oscillating cell) instead produces only TlmbMonitorInd, so the survey stalled on it forever and, worse, ran those indications through the serving-cell break/reopen machine (toggling out-of-service).

Fix: while surveying, divert TlmbMonitorInd out of the serving-cell path and use it as a bounded per-carrier backstop (SURVEY_MONITOR_TICK_LIMIT). A decodable SYNC resets the backstop so good-but-slow cells are never cut short; the SYNC/SYSINFO and scan-dwell paths still handle decodable and empty carriers. The survey now always advances and completes. Adds two regression tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-25 19:40:08 +02:00
misadeksandCopilot App 97412c8822 feat(ms): scan carrier offsets in frequency_list Range mode
Range lists enumerated only the nominal 25 kHz raster (offset 0). Add an optional offsets key to [frequency_list.range] so each carrier is also probed at the ETSI-legal offsets (0, +6250, -6250, +12500 Hz; EN 300 392-2 D-MLE-SYNC Offset field). candidate_frequencies() expands carrier x offset (deduped); validation rejects any offset outside the four legal values. Absent/empty offsets = [0], preserving prior behaviour. List mode already supports any offset via absolute Hz. Docs + example config updated.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-25 19:29:27 +02:00
misadeksandCopilot App 96a75b4b1d docs(ms): fix frequency_list range to single-bracket [frequency_list.range]
The range sub-table maps to range: Option<FrequencyRangeDto> (one range per list), so it must be a single-bracket [frequency_list.range] table, not a [[...]] array of tables. The [[frequency_list.range]] form the docs/example showed fails to parse (invalid type: sequence, expected struct FrequencyRangeDto). Corrected MS_CONFIG.md and example_config/config-ms.toml.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-25 19:16:51 +02:00
misadeksandCopilot App e13de82c10 MS: UI-driven manual cell survey + register-to-cell (P1-P4)
Adds an operator-driven manual cell-selection feature to MS mode: an
Auto/Manual selection-mode toggle, a receive-only survey of the programmed
[[frequency_list]] candidate carriers that reports each found cell, and a
register-to-cell camp with forced ITSI attach. MS-only; the BS air interface
is unchanged. Strictly per ETSI TS 100 392-2 v3.10.1 (cl. 18.3.4 cell
selection / 18.3.4.6 camp, D-MLE-SYNC 18.4.2.1, D-MLE-SYSINFO 18.4.2.2,
registration cl. 16.4); the survey UX is Plane-B implementation policy while
every per-cell parse/suitability action stays ETSI.

P1 - MLE survey engine (mle_ms.rs): single-pass candidate walk, survey mode +
survey_results/survey_pending, selection-mode gate (Manual suppresses
auto-camp), and 5 internal LMM-SAP primitives (SelectionModeReq, ScanReq,
CampReq, ScanResultInd, ScanCompleteInd). Receive-only: early-return guards
in the SYNC/SYSINFO handlers so the survey never camps/registers or filters by
allowed-network (it observes, it does not select). 8 unit tests.

P2 - MM management commands + telemetry (mm_ms.rs, management, net_control,
net_telemetry): 4 ManagementCommand variants (SetCellSelectionMode,
StartCellScan, StopCellScan, CampOnCell), MsScanResult/MsScanComplete
telemetry events, MsRuntimeState.selection_mode_manual, schema bumped to
bluestation-ms-interface-3 (append-only) with the golden wire-format freeze
test updated. The 4 commands are offline-serviceable (survey works before
registration). 6 unit tests.

P3 - CampOnCell + forced registration: camp_force_register forces an ITSI
attach even when the cell advertises registration-not-required; the carrier is
validated against the candidate set. The active-call guard is a documented
no-op stub (MM has no CMCE call-state signal yet).

P4 - docs: MS_MODE.md feature rows (MLE survey, register-to-cell) + MS_CONFIG.md
note that the survey reuses [[frequency_list]] (no new config keys).

Tests: tetra-entities 332 lib + integration green; bluestation-bs builds.
Software-tested only; not yet hardware-validated.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-25 18:43:02 +02:00
misadeksandCopilot App 4781f1b345 fix(ms): yield unsynchronized RX loop so offline config servicing runs before sync
The offline config pump (drive_offline_control) added previously was never
reached on real hardware: while the MS downlink is unsynchronized the
demodulator stays in Mode::DlUnsynchronized and never yields a slot, so
RxTxDevSoapySdr::rxtx_timeslot's RX loop blocked indefinitely and the
single-threaded run loop never iterated to service the control interface.

While the downlink is not synchronized, the RX loop now yields back to the
caller (returning with no slot) after a short wall-clock window (UNSYNC_YIELD,
20 ms), in addition to the existing cooperative-shutdown break. The run loop
then reaches its no-slot branch and services the offline-safe management subset
(GetConfig/SetConfig/ApplyConfig/GetState/GetInterfaceVersion), so a UI can read
and stage the codeplug before the MS has synced or registered. The demodulator's
correlation state persists across calls, so sync acquisition is unaffected; once
synchronized the loop returns each slot on its own and is byte-identical, and the
mechanism is never installed in BS mode.

Docs updated (MS_MODE.md 3.7, MS_CONFIG.md availability note).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-25 17:54:31 +02:00
misadeksandCopilot App c732045c2a MS: service management config read/staging before synchronization
The MS stack is receive-timed: MessageRouter::run_stack_ms only ticks the
entities once the PHY recovers a downlink slot (DL-synchronized). Before the
MS syncs to a base station no tick runs, so MM's control-command path
(poll_control in tick_start) never executes and management GetConfig/SetConfig/
ApplyConfig requests sit unserviced -- the UI sees an empty/absent config.

Add a pre-tick offline-servicing path so config read/staging works as soon as
the control link is up, independent of registration/service state:

- TetraEntityTrait::drive_offline_control (default no-op); MessageRouter calls
  it on every run_stack_ms iteration that recovers no slot.
- MmMs overrides it to service only the offline-safe management subset
  (GetConfig/SetConfig/ApplyConfig/GetState/GetInterfaceVersion) -- none inject
  SAP traffic needing the stack clock or a serving cell. Every other command
  (TNMM requests, scan-list toggles) is buffered in deferred_control and
  replayed, in arrival order, on the first real tick, so registration and
  on-air behaviour are byte-identical to never having run offline.

No config schema change, no secret-handling change (******** redaction/restore
preserved), no on-air/registration behaviour change. ApplyConfig staging/
restart semantics kept as-is.

Tests: 4 new lib tests (offline GetConfig round-trip, offline SetConfig persist
+restart flag, offline secret redaction, TNMM registration deferral-until-tick).
316 lib + all integration tests pass; bluestation-bs builds clean.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-25 17:23:34 +02:00
misadeksandCopilot App 5960b24f70 fix(ms): exit promptly on Ctrl+C when not synchronized to a BS
While the MS is unsynchronized the downlink demodulator stays in
Mode::DlUnsynchronized and never yields a demodulated slot, so the SoapySDR
rxtx_timeslot RX loop (while process_rx_block) spun until the MS camped on a
cell. The MS run loop only checks the stop flag between rxtx_timeslot calls, so
a Ctrl+C while not connected to a base station was deferred until a downlink
appeared -- the process appeared to 'await connection' before exiting.

Give RxTxDevSoapySdr a cooperative shutdown flag (the process-wide is_running,
wired only in build_ms_stack) and break out of the RX loop when a shutdown is
requested *and* the downlink is not synchronized. The break is gated on
!dl_synchronized so a shutdown while camped is byte-identical to before: the
loop still returns each slot on its own, letting MM transmit its U-ITSI DETACH
during the bounded drain. No-op in BS mode (no flag installed).

- soapy_dev.rs: run_flag field + set_run_flag(), stop_requested()/dl_synchronized()
  helpers, RxDsp::dl_synchronized(), DemodulatorChannel::is_synchronized().
- demodulator.rs: Demodulator::is_synchronized() (Mode::Dl).
- main.rs: wire is_running into the MS PhyMs SDR device.
- docs/MS_MODE.md: document the unconnected-shutdown behaviour.

Build (bluestation-bs) clean; 312 lib + all integration tests pass.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-25 02:04:37 +02:00
misadeksandCopilot App 165d70491d docs: reference the tetra-tn-web-ui companion UI repo
Link https://github.com/misadeks/tetra-tn-web-ui (the portable-radio web UI /
codeplug programming / ACELP vocoder client that drives the MS stack) from the
MS-mode README architecture section and the root README.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-24 23:59:12 +02:00
misadeksandCopilot App 3565f395a5 docs: add MS-mode README + config reference
Document the mobile-station (MS) implementation on this branch:

- docs/MS_MODE.md - architecture overview, per-layer feature matrix with
  implementation state (hardware-validated / software-tested / deferred /
  out-of-scope), on-air happy-path walkthrough, known limitations, build/run,
  and ETSI TS 100 392-2 v3.10.1 clause traceability.
- docs/MS_CONFIG.md - full MS configuration-file reference: every [phy_io],
  [net_info], [cell_info], [duplex_table], [ms], codeplug, [control] and
  [telemetry] key, keyed to the tetra-config section structs and cited to the
  relevant spec clauses, plus a minimal working config.
- README.md - link the new MS docs, the interface doc, and the example config.

Docs only; no code change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-24 23:56:56 +02:00