journald was rate-limiting bluestation-bs ("Suppressed ~6,200 messages" every
30s) because the stack logged a per-frame/per-burst firehose at debug/info by
default. Logging-only change; no protocol/timing/DSP behavior touched.
- debug.rs get_default_stdout_filter(): now honors RUST_LOG (full override, e.g.
RUST_LOG=debug|trace or per-module) and otherwise defaults to a quiet `info`.
Removed the hardcoded per-module `=debug` directives (umac/mle/llc/cmce/mm/...)
that forced the flood; kept messagerouter/bitbuffer=warn.
- Demoted the highest-rate lines so steady-state is ~silent and `debug` stays
readable:
- every entity's per-message `rx_prim: {:?}` dump: debug -> trace (phy/lmac/
umac/mle/mm/cmce/llc/sndcp, MS + BS).
- per-frame PDU decode dumps (MacSync/MacSysinfo/MacResource/AccessAssign/
AccessAssignFr18/DMleSync/DMleSysinfo `<- {:?}`): debug -> trace.
- per-frame `rx_broadcast_sysinfo: Updated TdmaTime`: info -> debug.
- per-burst PhyMs "queued uplink burst", Modulator(Ul) "pending burst vs TX
window", Modulator "uplink burst active part emitted (SN0)": info -> debug.
- Kept at INFO (low-rate, useful): startup/SDR-open/freqs, retune, downlink
synchronized, registration/cell-(re)selection/call/floor events, all warnings.
- example_config/bluestation-ms.service: Environment=RUST_LOG=info (documented
how to bump). Supervisor: export RUST_LOG="${RUST_LOG:-info}".
- docs/MS_CONFIG.md: new "Logging verbosity" section.
Acceptance: default config -> registered idle emits only occasional lines, no
"Suppressed N messages"; RUST_LOG=debug/trace restores full detail.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Field finding: with CPUSchedulingPolicy=fifo/prio 73 active and get_throttled=0x0,
the MS still flapped (serving-cell downlink failure / MLE-BREAK) whenever the
local Slint UI ran. The SXceiver (driver=sx / SoapySX) delivers samples over an
I2S/ALSA DMA path, and hard IRQ/softirq/kernel-DMA work preempts even a
SCHED_FIFO userspace thread — so real-time priority is necessary but not
sufficient; the fix is CPU isolation (keep the UI and routine IRQs off the
radio's cores).
- example_config/bluestation-ms-ui.service: sample unit for a co-located
operator UI, pinned to cores 0-1 (CPUAffinity=0 1, Nice=5) so it cannot
contend with the radio on cores 2-3.
- docs/MS_CONFIG.md: new "When FIFO priority alone is not enough" subsection —
the fast taskset confirmation test, kernel cmdline isolation
(isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3 irqaffinity=0,1), and steering the
I2S/DMA IRQ via /proc/irq/<n>/smp_affinity.
Deployment guidance only; not yet field-verified.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The MS is receive-timed: if the SDR RX pipeline is starved of CPU (e.g. a Slint
UI / desktop compositor sharing the Pi), samples are dropped ("Lost N samples,
skipping ..."), the demodulator loses sync, and the MLE declares a serving-cell
downlink failure (MLE-BREAK). The radio then flaps in/out of service and never
completes registration (T351 timeouts) even though RSSI is healthy and bursts
CRC-pass. Symptom: the MS connects fine until the UI is started.
Give the stack real-time priority so its RX thread preempts the UI, and pin it
to dedicated cores:
- example_config/bluestation-ms.service: add CPUSchedulingPolicy=fifo,
CPUSchedulingPriority=73, IOSchedulingClass=realtime, Nice=-10, LimitRTPRIO=99,
LimitMEMLOCK=infinity, CPUAffinity=2 3, and KillSignal=SIGINT. Comments explain
tuning (isolcpus, pinning the UI to the other cores) and that this is a
scheduling, not RF, problem.
- example_config/bluestation-ms-supervisor.sh: launch the binary under
`chrt -f $RT_PRIO` (default 73) and, when CPU_AFFINITY is set, `taskset -c`,
each best-effort with graceful fallback when the tool/privilege is missing.
- docs/MS_CONFIG.md: new "Real-time scheduling" section documenting the failure
signature and the fix (incl. UI-side guidance and vcgencmd get_throttled).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
TETRA has no on-air PABX/PSTN distinction; both are ordinary external-number
calls routed by the SwMI on the gateway subscriber + digits. The kind field
(and CfgGatewayKind enum) was a cosmetic UI label only, so remove it to keep the
codeplug lean. A gateway is now just id + name + gateway_issi + optional prefix.
Updated CfgGateway/GatewayDto/converters, example config, MS_CONFIG.md, and the
contacts/PSTN UI prompt. Config suite 64 green.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Analysis: in MS mode the stack reads nothing functional from [cell_info] — RF is
learned over the air (SYNC/SYSINFO, cl. 18.4.2), location_area is only a
pre-registration seed (overwritten by the registration confirm), and colour_code
just fills a cosmetic state-snapshot field. So [cell_info] is not needed for MS.
- Parser: root cell_info is now Option (serde default). BS mode still requires
it (existing main_carrier/freq_band guard); MS may omit the whole section and
the serializer drops it from canonical MS output.
- Reorganize example_config/config-ms.toml: drop [cell_info]; group ALL codeplug
sections into one numbered region (settings -> networks -> frequencies ->
carrier overrides -> folders -> talkgroups -> scanlists -> gateways -> contacts).
- net_info stays a shared top-level home-network section (used by both BS and MS);
documented its relationship to the codeplug [[network]] allowed-networks list.
- Tests: MS config without any [cell_info] parses/validates; MS round-trip omits
[cell_info]. Config suite 64 green (shipped BS + MS examples parse+validate).
- Docs: MS_CONFIG.md net_info/cell_info sections rewritten.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a scalar [codeplug] settings table with home_display_pid (8-bit SDS
protocol identifier, ETSI TS 100 392-2 cl. 29.4.3.9) for a 'home mode display'
status/text message. Codeplug data only (Plane B) — surfaced to the UI via the
existing GetConfig/SetConfig TOML round-trip; no on-air behaviour change.
- CfgCodeplug.home_display_pid: Option<u8> + is_empty + home_display_pid()
accessor; CodeplugSettingsDto ([codeplug] table) + codeplug_dto_to_cfg /
cfg_to_codeplug_settings_dto; parsing.rs root wiring.
- Example config [codeplug] section + MS_CONFIG.md docs.
- Test: settings round-trip via the DTO; config suite 62 green, shipped
example config parses+validates.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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>
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>
A radio-style MS is programmed by downlink and learns its serving carrier,
uplink and duplex spacing over the air from the cell's D-MLE-SYNC/SYSINFO
(EN 300 392-2 cl. 18.4.2). So the fixed soapy tx_freq/rx_freq and the
[cell_info] RF block are no longer needed in an MS config and were only
confusing.
- sec_phy_soapy: tx_freq/rx_freq optional; add dl_freq_seeded flag so a
seeded RX / unset UL round-trip without reintroducing fixed freqs.
- sec_cell: cell_info RF fields (main_carrier/freq_band/freq_offset/
duplex_spacing/reverse_operation) optional; serializer omits them for MS.
- parsing: seed the SDR initial RX (dl_freq) from the first [[frequency_list]]
scan candidate for MS; require cell RF + soapy freqs only for BS mode.
- config validate: gate the cell-derived DL/UL match check to BS; for MS
require some RX source (a frequency_list or explicit tx_freq); drop the
now-moot MS mismatch warning.
- soapyio/soapy_dev: leave MS TX unset (no TX chain) until ul_freq is known,
i.e. until the MS camps and derives the uplink from SYSINFO.
- example_config/config-ms.toml: remove tx_freq/rx_freq and the [cell_info]
RF block; explain the scan-driven / over-the-air model in comments.
Remove commitmsg.txt accidentally committed in the previous change.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace the single [scan] section with multiple named [[frequency_list]]
array-of-tables. Each list is List or Range (the old Fixed mode is removed
- a single-frequency setup is just a List with one frequency). The radio
scans all lists combined into one candidate set (deduplicated, order
preserved).
- sec_codeplug: ScanMode -> FrequencyListMode (List/Range only), CfgScan ->
CfgFrequencyList (+ name), CfgScanRange -> CfgFrequencyRange; codeplug field
scan: Option<CfgScan> -> frequency_lists: Vec<CfgFrequencyList>; add
scan_candidate_frequencies() combining+deduping all lists; per-list
validation (unique non-empty names, List needs >=1 freq, Range needs range).
- parsing: TomlConfigRoot.scan -> frequency_list array; DTO/serialize plumbing.
- mle_ms: scan_candidate_carriers() now returns the combined set; drop
ScanMode import.
- example_config/config-ms.toml: [[frequency_list]] with List + commented Range.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reframe the Plane B codeplug around how a portable radio is programmed,
not how a BS is defined:
- Talkgroups are the user-selectable groups (folders + explicit order
within a folder; talkgroups now carry an order field).
- Allowed networks move to a codeplug-wide [[network]] list (mcc/mnc +
optional name + selection priority), replacing the per-scan
allowed_networks.
- The scanner is driven by a list of absolute downlink requencies (Hz)
or a carrier range, replacing the channel-name reference list.
- The old [[channel]] becomes [[carrier_override]]: an optional
per-frequency override (colour-code lock, custom duplex, rx_only),
matched by downlink frequency; no folder/talkgroup binding.
Added helpers: folders_sorted, talkgroups_in_folder, networks_sorted,
carrier_override_for_freq, CfgScan::candidate_frequencies (expands Range).
Renamed StackState.last_camped_channel -> last_camped_freq_hz (u32).
Updated parsing DTOs/round-trip, example config-ms.toml, and tests.
All values remain ETSI-bounded and validated. Config stays v0.7; pure
crates + entities/bins suites green (199 lib + all integration).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a Plane B codeplug so an MS is programmed like a radio (by downlink +
allowed networks) rather than like a BS. Data only: read by the UI/scan
engine in later phases; no on-air behaviour change yet. Every value maps
to a real ETSI element and is validated against its bit-width/allowed set.
tetra-config (sec_codeplug.rs):
- CfgCodeplug = folders + channels + talkgroups + optional scan.
* Channel: band+carrier(+offset) OR absolute dl_freq (Hz), optional
colour-code filter (6-bit), duplex_index hint (3-bit), custom duplex
spacing, folder, rx_only, default talkgroups. DL math mirrors
FreqInfo::get_freqs (band*100MHz + carrier*25kHz + offset).
* Talkgroup: 24-bit GSSI, class_of_usage (3-bit), folder.
* Scan: Fixed/List/Range modes, dwell, allowed_networks (empty = home
MCC/MNC only; 10-bit MCC / 14-bit MNC).
* validate(): ranges + cross-refs (folder/channel/talkgroup ids).
- Full DTO round-trip (channels serialize in canonical band+carrier form).
Config wiring:
- StackConfig.codeplug; parsed from [[folder]]/[[talkgroup]]/[[channel]]/
[scan]; validated at parse with descriptive errors.
- config_version bumped to 0.7; 0.6 still accepted (legacy, no codeplug)
so existing configs and inline test configs keep loading.
- Relax MS/Mon validation: DL/UL derived-vs-soapy mismatch is now a
warning (a radio derives UL from the cell's SYSINFO at camp time,
EN 300 392-2 cl. 18.4.2.2); BS still requires an exact match.
- StackState.last_camped_channel for future fast re-acquire.
Example configs: bump to 0.7; config-ms.toml gains a [duplex_table],
folders, talkgroups, two channels (band+carrier and dl_freq forms) and a
[scan] block with a range-scan example.
Tests: tetra-config 35 (codeplug ranges/cross-refs/round-trip, dl_freq
resolution, legacy-version acceptance, shipped example configs parse +
validate). WSL: 199 lib + all integration suites green, no warnings.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Per maintainer decision #4, land the MS external-interface artifacts in-repo
under a dedicated examples/ms-interface/ directory (the source of truth), not
example_config/:
- examples/ms-interface/README.md — the protocol/API documentation (message
catalog): Plane A TNMM-SAP (cl. 15.3) + Plane B management, the HYBRID apply
model, redact-on-wire / preserve-on-write secret handling, and a wscat quick
example. Shippable API docs, not a planning note.
- examples/ms-interface/reference-client.py — the runnable language-neutral
reference client (concise docstring pointing at the catalog).
Removes the interim example_config/ms-ui-reference-client.py. Deployment
artifacts (systemd unit + supervisor script) stay in example_config/. This
directly serves the "build different UIs on top of the stack" goal. Docs/sample
only; no code or wire-behaviour change. py_compile clean.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add example_config/ms-ui-reference-client.py: a runnable, language-neutral
reference client for the MS external interface (schema bluestation-ms-interface-1)
whose module docstring also serves as the full message catalog (Plane A TNMM-SAP
cl. 15.3 + Plane B management). Documents the redact-on-wire / preserve-on-write
secret handling and the HYBRID apply model (SetConfig stages; ApplyConfig drains
+ exits 75 for the supervisor). NON-STANDARD Plane B clearly marked; no ETSI
behaviour asserted. Sample only, not built/linked; py_compile clean.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Complete the MS management interface (Plane B, NON-STANDARD) write path:
GetConfig / SetConfig / ApplyConfig, using the HYBRID apply model (structural
radio params staged to TOML + applied on a controlled restart; operational TNMM
actions stay live on Plane A). ETSI does not standardize radio programming over
the air, so this whole plane is implementation-defined and marked as such.
tetra-config — TOML write-back (inverse of from_toml_str):
- Derive Serialize on the on-disk DTO layer only (TomlConfigRoot, *Dto, StackMode)
— NOT on StackConfig/SecretField (SecretField redaction is for logs only).
- Add cfg_to_*_dto inverses for every section (net/cell/ms/phy/soapy/control/
telemetry/brew) mirroring the existing dto_to_cfg + apply_*_patch mappers, so
there is ONE schema source of truth and the written file re-parses through the
exact same validator (round-trip GetConfig->SetConfig->load provably closed).
- New parsing::to_toml_string(&StackConfig) -> canonical TOML; emits
config_version = "0.6" verbatim; secrets written as plaintext (the TOML file
is their canonical store). `extra` catch-alls skipped when empty (a loaded
config has no unknowns — the parser rejects non-empty extra).
- Tests: MS-config round-trip (parse->serialize->reparse, key fields incl. soapy
gains via non-empty flatten) + config_version emission.
tetra-entities — management commands + MM handlers:
- management: add ManagementCommand::{GetConfig,SetConfig,ApplyConfig} and
ManagementResponse::{Config,Ack}; add restart_required to MsRuntimeState.
- MmMs: GetConfig serializes the active config; SetConfig validates via
from_toml_str, normalizes via to_toml_string, writes to the config path and
sets restart_required (does NOT bounce the process); ApplyConfig reuses the
existing graceful de-registration drain (U-ITSI DETACH, cl. 16.6.1) then
signals main to exit with the restart code. Management context (config path +
shared restart/running flags) installed via a setter so existing test
constructors are untouched; write/apply refuse gracefully when absent (keeps
the "external UI must never panic the RF process" invariant — validate/reject,
never unwrap).
- Tests: GetConfig round-trip, SetConfig without-context refusal, SetConfig
validate+persist+flag-restart, SetConfig invalid-config rejection, ApplyConfig
drain+restart-signal, ApplyConfig without-context refusal, plus a codec JSON
round-trip for the new config variants.
bin + supervisor:
- main.rs: EXIT_RESTART = 75 constant; create restart/running flags before
building the stack and thread them + the config path into build_ms_stack;
after run_stack, exit(75) if a restart was requested. In-process relaunch is
deliberately avoided (main owns the SDR handle + thread graph).
- Sample supervisor artifacts (example_config/): systemd unit
(RestartForceExitStatus=75) and a POSIX wrapper-script loop.
BS code paths unchanged (Serialize derives are mechanical; no behaviour change).
Note (non-blocking): CfgBrew round-trips asymmetrically — DTO username:u32 vs
runtime Option<String> (parsed back, defaults 0) and DTO password:String vs
runtime Option<SecretField> (written plaintext). Brew is BS-only; MS configs
carry no [brew] section, so this never affects the MS write path.
Build+test: tetra-config green on Windows; tetra-entities 167 passed / 5 ignored
and bluestation-bs builds in WSL.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Example configuration for MS mode connecting to a BS: DL/UL freqs derived from
freq_band 4, main_carrier 1593, custom duplex spacing 9.4 MHz, SXceiver gains,
and net/cell identity (MCC/MNC, colour code, LA). Used by the MS build-deploy
scripts.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Add [ms] config section (CfgMs: issi, subscriber_class, attach_groups),
wired through parsing/validation; reuses net_info/cell_info for radio.
- Add minimal PhyMs<D: RxTxDev> skeleton registered as the Phy entity
(receive-driven; RX demod + RX-driven clock deferred to Phase 1).
- Add build_ms_stack() and switch on stack_mode in main() (single bin).
- Reconcile stale mle_ms.rs against the current SapMsg/SAP API (mirrors
mle_bs) and declare it in mle/mod.rs so MleMs compiles/registers.
Grounded in ETSI TS 100 392-2. No air behaviour yet (assembly only).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Per-radio ISSIs with TetraPack safety filtering; MM improvements
- Version to 0.4.9
- Send per-radio REGISTER/DEREGISTER/AFFILIATE/DEAFFILIATE with individual ISSIs
- Add TetraPack ISSI filter: only exactly 7-digit ISSIs sent over WS
- Restrict GSSI <= 90 filter to TetraPack only
- Filter GROUP_TX forwarding for non-routable source ISSIs
- Remove Brew ISSI config field and initial BTS registration
- Rename is_brew_routable to is_brew_gssi_routable
- MM: auto-register unknown MS on group attach
- MM: send D-LOCATION UPDATE COMMAND to returning radios for group report
- MM: fix group_identity_attachment_lifetime to ETSI default
- MM: rename TemporaryRegistration to MigratingLocationUpdating
* implemented LST indication based on connection to Brew or enablement of Brew
With brew disabled, LST signalling follows optional user flag
With brew enabled, LST signalling dynamic, overriding user flag
* added Brew WebSocket ping/pong heartbeat
Co-authored-by: Wouter Bokslag <w.bokslag@midnightblue.nl>
- Better support for uplink SCH/F
- More typed message parsing improvements and standardization
- MAC Scheduler bugfix, restructuring
- Improved stdout formatting
- Added debugging features; logging text and UL/DL RF samples to file
- Implemented various extra PDU types
- Added unit tests and test vectors for various PDU types
- Added near-full-stack tests for UMAC and above.
You can now use the optional custom_duplex_spacing field to override the duplex spacing (distance between ul and dl) instead of using the values as specified by ETSI. This is useful if your radio uses a modified duplex spacing table.