Skip to content

Releases: nigelfenton/flex-sim

v0.3.6 — several clients at once (MultiFlex + bound controllers)

Choose a tag to compare

@nigelfenton nigelfenton released this 22 Oct 04:06
e34dfc7

The Flex radio sim now serves several API clients at once, as a real FlexRadio does. A controller can sit beside AetherSDR and drive its slices, for example a CTR2 through AetherSDR's CTR2 Relay. With only AetherSDR connected, nothing changes.

About the binaries below: they are the Flex radio sim (flex_sim.py). The KPA1500, ANAN and station sims run from source.

Added: several clients at once (#24)

  • Each connection is served on its own thread, with its own client handle (V/H greeting) and its own send lock. The radio's slices, panadapters and transmit state stay shared.
  • GUI and non-GUI clients: a client that sends client gui is a GUI client. sub client all reports client 0x<handle> connected … client_id=<uuid> program=… station=… for each one, and disconnected when it leaves.
  • client bind client_id=<uuid>: a non-GUI controller binds to a GUI client, then sees and drives that client's slices (slice tune, slice set … mode=, audio_level=, filter, xmit / transmit set mox=). Status reaches every client that should see it.
  • Disconnects are per client: only the leaving client's own slices and pans are removed, and TX is dropped only if that client keyed it.

Fixed: autopan=1 re-announces the pan (#25)

slice tune <n> <MHz> autopan=1 now re-sends the panadapter status with its new centre, as display pan set center= already did. Before, AetherSDR kept the old centre while the sim framed the spectrum on the new one, so the carrier pattern appeared away from the slice flag.

Added: sim get / sim set level= (#25)

Flex sim-only API verbs, so a controller without access to the web control panel can read and set the signal level (the same value as the panel's signal-level slider):

  • sim get replies level=<dBm> floor=<dBm> pattern=<name>.
  • sim set level=<dBm> sets it, clamped like the slider.

A real radio rejects them. Documented in PROTOCOL.md as a flex-sim extension.

Tested with

AetherSDR's CTR2 Relay and a CTR2-Max running the AetherPanel firmware:

  • the controller binds to AetherSDR's session
  • the VFO tunes with autopan=1 and the pan follows, with the carrier on the flag
  • push-and-turn sets the signal level
  • PTT and TUNE key the sim only

v0.3.5 — Flex sim SmartSDR v4.3 firmware (--firmware 4.3)

Choose a tag to compare

@github-actions github-actions released this 14 Oct 02:27
e2e3eb7

The Flex radio sim can now behave like a radio on SmartSDR v4.3.16, FlexRadio's release of 2026-10-13. Start it with --firmware 4.3 to test AetherSDR's 4.3 work without upgrading a radio. aethersdr/AetherSDR#6437 maps the wire changes this follows. Without the option nothing changes: the sim reports and behaves as before.

About the binaries below: they are the Flex radio sim (flex_sim.py). The KPA1500, ANAN and station sims run from source.

Added: --firmware 4.3 (#19)

  • Version: discovery and the connect greeting report 4.3.16.42034. Presets are 3.3, 4.1, 4.2 and 4.3, or give a full version.
  • PTT Override (#6442): the transmit status reports ptt_mic_override. A client can set it to 0 or 1; any other value is refused. Older firmware never reports it.
  • What a footswitch sends. On 4.3, a hardware PTT (MIC, ACC or RCA) transmits the selected source, DAX included. Before 4.3 it always forced the analog mic. PTT Override brings the old behaviour back.
    • Simulate a footswitch with http://<sim>:8731/hwptt?src=MIC&on=1.
    • /state shows what the radio would put on the air.
  • Interlock sources: every latched source is listed, hardware first (MIC,VOX, MIC,VOX,SW), and VOX is reported as VOX.
  • DSP levels: reported as nrl_level, anfl_level and nrs_level. NRS runs 0–50, and nrf_level is ignored.
  • 48 and 96 kHz slice audio: slice set N sample_rate=48000|96000 streams with the new class codes (0x03E4/0x03E5, reduced bandwidth 0x0124/0x0125). It switches live.
  • APD: state=OFF|IDLE|ACTIVE, plus the new equalizer line with its antenna, frequency and power.
  • UDP keepalives: udp_register and ping are counted in /state.
  • DAX TX rate: the TX chain check gains stage 3b: TX audio rate must match the TX slice, which 4.3 firmware enforces.
  • CWX: the 0x1E stand-in for a quote inside cwx send is decoded.

Fixed

UDP keepalives were counted as DAX TX audio. AetherSDR sends its UDP keepalives to the radio's port 4991, which is also where the sim watches for DAX TX audio. So a client ping every 5 s could make the TX chain's "audio arriving" stage flicker true with no audio flowing. Keepalives are now recognised and counted separately, whatever the firmware setting.

Tested

  • tests/test_firmware_43.py: 44 checks, each behaviour run against an old-firmware sim and a 4.3 one.
  • The full suite and the offline loopback self-test pass.
  • Against a real AetherSDR: the #6292 build connected to a --firmware 4.3 sim, showed version 4.3.16.42034, and took the new PTT Override key without complaint.

Documented in PROTOCOL.md §12.

One assumption: whether real 4.3 firmware ignores the dropped nrf_level command or refuses it isn't known. The sim ignores it.

Not covered: SmartLink WAN sessions, APD's TRAINING/HOLD/FAULT states, and the FLEX-8000/Aurora-only features (AutoDX, NTP, Noise Floor Range).

v0.3.4 — Flex sim licensing options: subscription, features, DVK refusal, live changes

Choose a tag to compare

@github-actions github-actions released this 13 Oct 05:02
8b3a338

The Flex radio sim can now report any licence a real radio might: SmartSDR+ subscription, licensed features and DVK refusal. You set it at startup or change it live while AetherSDR is connected. This was asked for by K5PTB, to test AetherSDR against different licences without a radio for each one.

About the binaries below: they are the Flex radio sim (flex_sim.py). The KPA1500, ANAN and station sims run from source.

Added (#17)

  • The full licence report. sub license all is answered with the three licence objects a real radio sends:

    • license
    • license subscription
    • one license feature line per feature

    By default the sim reports an active SmartSDR+ subscription with the Digital Voice Keyer licensed.

  • Startup flags:

    • --subscription none|smartsdr+|early-access
    • --subscription-expires YYYY-MM-DD (a past date gives an expired subscription)
    • --license-feature NAME=0|1[:REASON], repeatable, for any feature
    • --dvk-refuses

    --no-dvk-license still works.

  • Live changes, no reconnect. http://<sim>:8731/license changes the licence while AetherSDR is connected and pushes the new status straight to it. For example, /license?feature=digital_voice_keyer&enabled=0 or /license?subscription=none. With no query it shows the current licence.

    • Bad input is refused and changes nothing.
    • Rack mode takes &radio=N.
  • DVK refusal. With --dvk-refuses (or ?dvk_refuses=1), every dvk command is refused with 50004001. A real radio does this when it won't store recordings, sometimes even while it reports the feature as enabled.

tests/test_licensing.py covers all of this (20 checks), and PROTOCOL.md §11 documents it.

Checked end to end with the aethersdr/AetherSDR#6292 build. With the voice keyer source on Automatic:

  • AetherSDR uses the Radio keyer while the licence is on.
  • Turning the licence off live switches it to Local and removes Radio DVK from the menu.
  • Turning the licence back on switches it back to Radio.

Testing tips:

  • Start from Automatic. A saved Local or Radio choice overrides the licence.
  • AetherSDR only upgrades its subscription label during a session, so reconnect to test a downgrade.

v0.3.3 — Flex sim TX audio state: mic source, DVK licence, reconnect and client-drop

Choose a tag to compare

@github-actions github-actions released this 11 Oct 23:12
9ef052f

Five fixes to the Flex radio sim's transmit-audio state. These are behaviours that AetherSDR's client-side voice keyer (aethersdr/AetherSDR#6292) depends on. Before this release, testing that PR meant patching the sim first. Anyone testing AetherSDR's mic source, DVK or TX recovery against the sim should update.

About the binaries below: they are the Flex radio sim (flex_sim.py). The KPA1500, ANAN and station sims run from source.

Fixed (#15)

  • Mic source. mic list now includes PC like a real 6000. mic input <src> selects the source, and the transmit status reports mic_selection= and dax= so a client can read them back. AetherSDR's voice keyer refuses to record unless the radio reports PC, so recording could not be tested before.
  • DVK licence. The sim now answers sub license all with digital_voice_keyer enabled. The new --no-dvk-license flag makes it a radio without SmartSDR+. AetherSDR assumes the licence is present when it hears nothing, so every sim used to look licensed, and the unlicensed path (the one the client-side keyer exists for) could not be reached.
  • remote_audio_tx gets its own stream id. It used to share netcw's, so AetherSDR held one id for two streams.
  • Reconnecting while keyed. The sim answers sub tx all with its current TX state, so a client that reconnects mid-transmission learns that the radio is keyed.
  • A client that disconnects while keyed is unkeyed, as on a real radio. The sim used to stay keyed, so the next client inherited a carrier nobody owned.

tests/test_tx_audio_state.py covers all five (11 checks), and PROTOCOL.md §10 documents them. Checked end to end with the #6292 build and no patches:

  • with --no-dvk-license, AetherSDR picks its Local keyer by itself
  • the mic switches to PC and reads back from the radio
  • a client dropping while keyed leaves the sim unkeyed

v0.3.2 — Flex sim TX routing for TCI; KPA1500 amplifier sim

Choose a tag to compare

@github-actions github-actions released this 10 Oct 21:06
38bec1d

Fixes to how the Flex radio sim handles transmit routing. This release also brings the new KPA1500 amplifier simulator. Anyone driving the Flex sim through TCI, or from an API client other than AetherSDR's GUI, should update.

About the binaries below: they are the Flex radio sim (flex_sim.py). The KPA1500, ANAN and station sims run from source.

Flex sim: transmit routing (#13)

Found while driving AetherSDR 26.10.1's TCI server from CAT4OM, a third-party CAT hub, with flex-sim as the radio. Each gap made a correct client look broken:

  • xmit 1 / xmit 0 now key the radio. AetherSDR turns a TCI trx:0,true; into xmit 1, and so do other API clients. The sim ignored it, so the radio never reported keyed and AetherSDR correctly dropped the PTT after its confirmation timeout. A malformed xmit keys nothing.
  • A new slice is announced before the slice create reply. AetherSDR's TCI VFO-B route looks the new slice up when the reply arrives. It found nothing, abandoned the slice and created another on the next request, so slices leaked (A, B, C …).
  • slice set N tx=1 moves the transmit slice. The sim ignored it, so TCI split never reached AetherSDR.

tests/test_tx_routing.py covers all three (19 checks; 10 fail on v0.3.1), and PROTOCOL.md §9 documents them. With these fixes, AetherSDR + CAT4OM passed 19/19 end-to-end checks against the sim. These covered frequency, mode, VFO B, split, PTT, and releasing TX when the controlling client drops.

New: KPA1500 amplifier simulator (kpa_sim.py)

An Elecraft KPA1500 built against Elecraft's Programming Reference V3. It is for aethersdr/AetherSDR#4097: can a KPA1500 be keyed safely over the LAN?

  • Network PTT (#9): ^TX; / ^TXnn; (keyed with a timeout) / ^RX; / ^TQ; on TCP and UDP :1500, with a T/R interlock bench. Run flex_sim.py --rf-sense 127.0.0.1:1510 so the radio tells the amp when its RF is on.
  • ^NI and the ACC INHIBIT line (#10).
  • The internal ATU (#12): ^AM ^AI ^AE ^FT ^FE ^TP, for aethersdr/AetherSDR#5905.

Also

  • Run the sims as always-on services on Linux (#11): example systemd units in deploy/systemd/ and a README section.

v0.3.1 — ANAN sim: real I/Q handedness

Choose a tag to compare

@nigelfenton nigelfenton released this 16 Sep 22:01
a054153

A correctness fix for the ANAN-G2 / openHPSDR Protocol 2 simulator. Anyone using anan_sim.py from v0.3.0 should update.

About the binaries below: they are the Flex radio sim, rebuilt with only a version bump. The ANAN / Protocol 2 tools run from source.

The fix

  • The sim's I/Q handedness was mirrored. On the first run against AetherSDR's ANAN-G2 backend (aethersdr/AetherSDR#5143), the "+1 kHz" test tone was drawn 1 kHz below the dial and was audible only in LSB. AetherSDR's polarity had been measured on a real G2 against WWV and cross-checked with an RSP1B running SDR++, so the sim was the one in error: the real HPSDR wire is the conjugate of the textbook convention. A correct client now shows the tone just above the dial and hears it in USB (#8).
  • Why it matters: a mirrored sim would push a correct client towards a "fix" that breaks it on real hardware.
  • Caught by a test now. tests/test_anan_p2.py asserts the tone's signed frequency; nothing checked it before, because every frame was the right size.

Also

  • High Priority frequencies are decoded as phase words (Hz = word × 122.88 MHz / 2³²), so a client tuned to 10.000 MHz is no longer logged as "349.525333 MHz". Discovery advertises it (byte 21 = 1).
  • The README's "not yet run against AetherSDR's ANAN backend" warning is retired — it now has been, end to end.

v0.3.0 — ANAN / Protocol 2, a whole station, and a noise bench

Choose a tag to compare

@nigelfenton nigelfenton released this 16 Sep 21:31
6bc99dd

The biggest update since the test bench. flex-sim grows a second radio family — an Apache Labs ANAN-G2 / openHPSDR Protocol 2 receiver simulator — plus a whole station of accessory simulators, an HF noise bench for testing noise reduction, radio-side notch filters, a TX slice, and a real CI suite.

About the binaries below: they are the Flex radio sim (flex_sim.py). The ANAN / Protocol 2 tools and the accessory simulators run from source — pure Python standard library, no install. See the README.

ANAN-G2 / openHPSDR Protocol 2 (new)

  • anan_sim.py — answers discovery, completes the General → DDC-Specific → DUC-Specific → High-Priority handshake, and streams DDC I/Q at the sample rate the client commands. Enough for a Protocol 2 client to find the radio and paint a panadapter and waterfall. Receive only.
  • Checked against more than itself. RX geometry confirmed against real ANAN-G2 captures (16 B header + 1428 B = 238 samples — the widely-quoted 1440 B / 240 is the TX layout, #3); an independent probe (tools/p2stream.c) built from piHPSDR's offsets; and an end-to-end session with NereusSDR 0.5.2, which found four sim defects the documentation-derived checks could not — session-port streaming, the DDC enable mask, commanded re-rating, and Windows pacing (#4, #6; reported in #5).
  • p2verify.py — a stdlib pcapng reader that reports what a real Protocol 2 radio puts on the wire, and tells DDC (RX) from DUC (TX) by each frame's own declared fields rather than by port. It guards against nanosecond pcapng timestamps and multicast traffic hiding the session (#7).
  • ⚠ Not yet run against AetherSDR's ANAN backend (aethersdr/AetherSDR#5143) — which is exactly what it is for. Reports welcome. One observation remains open: an RX-audio click comb in NereusSDR with a clean wire (#5).
  • The scoping doc is corrected: it had the RX packet geometry wrong (#7).

A whole station, no hardware

  • SPE Expert amplifier sim (spe_sim.py, :4531), including the ser2net / RFC 2217 power-on path, and a scripted wire bench (spe_bench.py).
  • ACOM 600S amplifier sim (acom_sim.py, :9600).
  • station.py now hosts SPE and ACOM alongside the Antenna Genius, Power Genius XL and Tuner Genius XL — five accessories, one process, one prompt — plus --radio-port and --pattern for the radio it spawns.
  • Amplifier meters published, with per-unit meter scaling fixed.

HF noise bench

  • Additive HF noise for testing AetherSDR's noise reduction, with text-to-speech voice fixtures so there is something realistic to recover.
  • The noise scene renders on the waterfall, tones sit at their true frequency via the live span, and noise rises from the floor rather than floating as a band above it.

Radio-side emulation

  • Tracking Notch Filter and Auto-Notch Filter, emulated end to end.
  • A TX slice (tx=) and 2-SCU diversity RX.
  • Radio-side two-tone generation and --txlog.
  • DAX TX chain observer — names the first missing stage rather than the symptom — with multiFLEX contention modelled.

Models, CLI and wire

  • FLEX-8400 added and the FLEX-8600 slice count corrected — thanks @Ozy311 (#1).
  • --models selects the model for a single radio too; the real model is logged.
  • --serial prefix, and the serial now defaults to the hostname, so two sims on one network are distinguishable.
  • Waterfall tiles encode VitaFrequency (Hz × 2²⁰), following AetherSDR #4412's removal of the plain-Hz fallback.

Fixes

  • sig_cw keys real Morse — duration and key state had been conflated.
  • SPE: a partial sync run is carried across receive boundaries.
  • The accessory sims now ship, so station.py runs from a clean clone.
  • The control panel's noise controls were dead; rebuilt.

Docker and CI

  • The Docker image ships every sim, not just the radio.
  • The test suite actually runs now. It was previously uncollectable — pytest reported "no tests ran" and exited 0. It runs on every push and PR on Linux and Windows, and CI fails if it ever collects fewer than four tests again.

v0.2.0 — test bench + realism

Choose a tag to compare

@nigelfenton nigelfenton released this 22 Jun 20:15
9bd05f8

A big usability + realism update. flex-sim's control panel is now a proper test bench, and the sim can drive AetherSDR's RADE/digital RX path.

Test bench (control panel)

  • Run calibration rulers from the panel — per-ruler checkboxes + a Run button; results stream into the page and reports are listed in-browser (no more SSHing in). The fast runner uses its own mock-AE loopback and coexists with a live sim.
  • "Live test on real AE" — a guided walkthrough that drives YOUR connected AetherSDR through each ruler (~5 s each, pausable), so you watch each signal appear on the panadapter/waterfall while the measured-vs-expected numbers stream alongside. Seeing is believing.
  • Fixtures dropdown — pick a WAV from fixtures/ (size + duration shown) instead of typing a server path.
  • Grouped patterns — dropdown organised into Realistic / Calibration rulers / Diagnostic / Keying.

Real Stop/Go + status

  • Stop/Go button — pauses ALL output (spectrum + audio) while staying connected to AE, so the panadapter and waterfall go dead; Go resumes. (The old "Stop" only changed the pattern.)
  • Live connection-status bar — waiting-for-AE / connected (with peer IP) / streaming <pattern> · dBm, or a clear "output STOPPED".

Realism

  • SSB voice lookalike pattern — a ~2.4 kHz USB-offset band with a syllabic envelope, word-gap pauses and drifting formants, so the waterfall reads like someone talking on HF (zoom AE's span in to see the detail). Sits alongside the geometric calibration patterns.

RADE / digital RX

  • dax_rx stream support — the sim now answers AE's stream create type=dax_rx, registers the stream, and streams full-bandwidth float32 so AE's RADE/digital decoder gets a valid signal. Modem decode proven (CRC-valid EOO callsign).

Other

  • Audio defaults to silence — the sim no longer blasts a 440 Hz tone on connect; Tone/Noise/WAV are opt-in.

Prebuilt binaries for windows-x64 / linux-x64 / linux-arm64 / macos-arm64 are attached once CI finishes.

v0.1.1

Choose a tag to compare

@github-actions github-actions released this 16 Jun 20:16

flex-sim v0.1.1 (beta) — a synthetic FlexRadio-6000 emulator / hardware-free test bench for AetherSDR. No radio required.

New in 0.1.1: Linux ARM64 binary — runs on a Raspberry Pi and other ARM boxes.

Download & run

Grab the binary for your machine, run it on a computer on your network (ideally not the same one as AetherSDR — see the README's one networking rule), and AetherSDR discovers it as a FLEX-6600. Then open the control panel at http://<that-ip>:8731/.

  • Windows: flex-sim-windows-x64.exe
  • Linux (PC / x86-64): flex-sim-linux-x64
  • Linux (Raspberry Pi / ARM64): flex-sim-linux-arm64
  • macOS (Apple Silicon): flex-sim-macos-arm64

⚠️ Antivirus may flag the .exe

It's unsigned, so SmartScreen / Norton / Defender may warn or quarantine it. This is a false positive — PyInstaller bundles Python into one file and trips AV heuristics. The full source is in this repo (pure-stdlib Python, zero deps); allow the file, or run from Python to skip the binary entirely.

See the README for full setup, rack mode, and the control panel.

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 16 Jun 19:44

flex-sim v0.1.0 (beta) — a synthetic FlexRadio-6000 emulator / hardware-free test bench for AetherSDR. No radio required.

Download & run

Grab the binary for your OS below, run it on a computer on your network (ideally not the same one as AetherSDR — see the README's one networking rule), and AetherSDR will discover it as a FLEX-6600. Then open the control panel at http://<that-ip>:8731/.

  • Windows: flex-sim-windows-x64.exe
  • Linux: flex-sim-linux-x64
  • macOS (Apple Silicon): flex-sim-macos-arm64

⚠️ Antivirus may flag the .exe

It's unsigned, so SmartScreen / Norton / Defender may warn or quarantine it. This is a false positive — PyInstaller bundles Python into one .exe and that trips AV heuristics. The full source is in this repo (pure-stdlib Python, zero deps); allow the file, or run from Python to skip the binary entirely.

See the README for full setup.