Multi-radio tune: SunSDR/TCI backend + client-selected radio config - #4
Open
VU3ESV wants to merge 2 commits into
Open
Multi-radio tune: SunSDR/TCI backend + client-selected radio config#4VU3ESV wants to merge 2 commits into
VU3ESV wants to merge 2 commits into
Conversation
spe-remote previously opened the SmartSDR TCP session at startup and held it for the whole life of the process. That kept the radio marked "in use" by spe-remote even when nobody was tuning, and meant the radio had to be on at startup (and spe-remote restarted if it was ever power-cycled). Make the Flex connection on-demand instead: - New spe/flex_controller.py: FlexController owns at most one FlexConnection, opened/closed on demand under an asyncio.Lock. connect() resolves the host lazily (static flex.host, else UDP discovery) so the radio may be off at startup and still be found later; both connect() and disconnect() are idempotent and never raise. Transitions broadcast as tune_event phases FLEX_CONNECTING / FLEX_CONNECTED / FLEX_DISCONNECTED / FLEX_ERROR. - TuneOrchestrator acquires the connection at the start of tune_single / tune_band and disconnects in the finally — so the radio is released as soon as the cycle or sweep is over, even on failure/abort. A sweep holds one connection across all sub-bands. - websocket_handler: new flex_connect / flex_disconnect commands. The web dashboard sends flex_connect when the Sweep panel opens and flex_disconnect when it closes while idle (never mid-tune). Both are no-ops — not forwarded to the serial handler — when Flex is disabled. - server.py: build a FlexController (no startup connect); the lazy connect at tune start keeps clients that never send flex_connect working. - web/app.js: pre-warm on Sweep-menu open, drop on idle close; FLEX_* events show pre-tune status without clobbering a finished sweep result. - flex.py: add FlexConnection.is_connected. - README: document the on-demand lifecycle and the new WS commands. Backward compatible: older clients (MacExpert, Node-RED, Vue) that only send tune_band/tune_stop still work via the lazy connect-at-tune-start safety net and get the disconnect-when-done behaviour for free. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generalises the orchestrated TUNE / band sweep beyond FlexRadio so it can
also drive an Expert Electronics SunSDR / ExpertSDR3 over TCI, and lets a
client pick & configure the active radio at runtime (no restart). TCI
command set verified against github.com/sm5tog/sm5k-spe-tuner.
Radio abstraction:
- spe/radio.py: RadioConnection ABC — the small primitive set the tune
orchestrator needs (connect/close, set_frequency MHz, set_mode, set_tune
_power, tune_carrier, snapshot/restore).
- spe/flex.py: FlexConnection implements RadioConnection (thin adapters over
the slice commands; VFO snapshot/restore moved here from the orchestrator).
- spe/tci.py: TciConnection — async TCI client over tornado's WebSocket
client (no new dependency). vfo:/modulation:/tune: commands; trx TX cache.
- spe/radio_controller.py: replaces FlexController. Builds the backend by
radio.kind (flex|tci|none), keeps the on-demand connect/disconnect
lifecycle, and adds reconfigure() for live radio switching. Status phases
are now generic RADIO_CONNECTING/CONNECTED/DISCONNECTED/ERROR.
- spe/tune_orchestrator.py: drives the generic interface; same proven
sequence (SPE TUNE keycode → RCU LED → carrier → LED off → carrier off).
Config + client control:
- spe/config.py: RadioConfig{kind} + TciConfig; parse radio:/tci: sections;
back-compat derives kind from flex.enabled; persist_values() — a generic
comment-preserving YAML writer (generalises persist_temperature_unit).
- websocket_handler: get_config + set_radio_config:<json> (live switch +
persist, refused mid-tune), radio_connect/radio_disconnect (flex_* kept
as aliases). server.py builds a RadioController from radio.kind.
- config.yaml: radio: + tci: sections (flex: retained). configtool.py:
radio.kind + tci.* added to SUPPORTED.
- web dashboard: RADIO settings panel (pick rig + edit host/port) wired to
get_config/set_radio_config; FLEX_*→RADIO_* event handling.
Docs:
- docs/CLIENT_RADIO_CONFIG.md: WS contract + config schema + MacExpert UX
spec (the client app is updated separately).
- README: multi-radio + client-driven radio config sections.
Backward compatible: existing flex-only configs keep working (kind derived
from flex.enabled); flex_connect/flex_disconnect and FLEX_* still accepted.
Stacks on feat/flex-on-demand-connection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Extends the orchestrated TUNE / band sweep beyond FlexRadio to Expert Electronics SunSDR / ExpertSDR3 over the TCI protocol, and lets a client pick & configure the active radio at runtime (no restart) instead of it being hard-coded in
config.yaml. TCI command set verified against the sm5tog/sm5k-spe-tuner reference.The tune sequence is unchanged and proven — only the per-rig commands differ:
slice t <s> <MHz>vfo:<trx>,0,<Hz>;slice s <s> mode=CWUmodulation:<trx>,CW;transmit tune on/offtune:<trx>,true/false;How
spe/radio.py(RadioConnectionABC) with the small primitive set the orchestrator needs.FlexConnectionimplements it (thin adapters; VFO snapshot/restore moved into the backend). Newspe/tci.py(TciConnection) is an async TCI client over tornado's WebSocket client — no new dependency.RadioController(replacesFlexController) builds the backend byradio.kind(flex|tci|none), keeps the on-demand connect/disconnect lifecycle, and addsreconfigure()for live radio switching. Status phases are now genericRADIO_CONNECTING/CONNECTED/DISCONNECTED/ERROR.RadioConfig{kind}+TciConfig; back-compat deriveskindfromflex.enabled. Newpersist_values()(generic comment-preserving YAML writer). WS:get_config+set_radio_config:<json>(live switch + persist, refused mid-tune);radio_connect/radio_disconnect(withflex_*aliases). Bundled dashboard gains a RADIO settings panel.docs/CLIENT_RADIO_CONFIG.md(WS contract + config schema + MacExpert UX spec; the client app is updated separately) and README.Backward compatibility
Existing flex-only configs keep working (
kindderived fromflex.enabled);flex_connect/flex_disconnectandFLEX_*still accepted. Flex command output is byte-for-byte unchanged (verified by test).Testing (local)
TciConnectionemits exactlyvfo:0,0,14025000;/modulation:0,CW;/tune:0,true|false;and converts MHz→Hz; snapshot from the TCI event cache.RadioControllerbuilds flex vs tci by kind,reconfigure()switches live, kind=none returns None.config.pyloadsradio:/tci:, back-compat kind derivation,persist_values()preserves comments (round-trip).py_compile/ import graph.Stacks on #3 (the on-demand-connection PR). Until #3 is merged, this PR's diff also contains that commit (
5dc5d9b); it'll reduce to just the multi-radio commit once #3 lands. Merge order: #3 first, then this.