Skip to content

Releases: nigelfenton/Aether-gate

Aether-gate v0.5.1 — true bin width, per-device dBm, and a gate that admits when the radio is gone

Choose a tag to compare

@nigelfenton nigelfenton released this 06 Sep 18:01

Aether-gate presents any radio to AetherSDR as a Flex 6000. This release makes the panadapter's
advertised bin width true, replaces one global dBm guess with per-device calibration, exposes the
SDR controls the Flex protocol has no verb for — and stops a gate with no radio from advertising
one anyway.

This is the first published release since 0.4.3. 0.5.0 was tagged but never released here, so
everything from both tags is below.

If you are running 0.4.3, take this one

A gate whose radio was absent advertised a FLEX-6700 regardless. With a Radioberry powered off,
the gate came up, logged board=0x00, advertised a radio, and AetherSDR connected and sat on
"Connecting to radio…" — black waterfall, and a full TX surface on a radio that was not there.
device_lost was declared on RadioAdapter but only ever set by soapy.py, so the two guards in
core/engine.py that exist to prevent exactly this were dead code for every other adapter; both
read it through getattr(..., False) and silently did nothing. Fixed by promoting the signalling
into the base class, with the clock measured from the last evidence of life rather than the first
silent call. open() also no longer trusts --radio-ip blindly — that flag short-circuited the
discovery check in the exact configuration every systemd unit ships.

Panadapter and levels

  • The advertised bin width is now true. The FFT was given a sample count that did not match the
    bin width being claimed.
  • dBm calibration is per-driver, not one global guess — the dBFS→dBm anchor is keyed by driver,
    with --dbm-base to override and --dbm-trim so a found calibration survives a restart. The
    panadapter and the S meter share one calibration instead of disagreeing.
  • The S meter measures the demodulator's passband, not total passband power, and reports the
    noise floor and SNR rather than signal alone.
  • Level tracking follows the hardware — gain changes made through a device setting are
    followed, and an LNA-state change warns instead of silently miscorrecting.
  • Wide frames segment across datagrams instead of being capped at one, and bin width is
    dialable from the control panel. AE's pan zoom drives the SDR's sample rate.

Radio control

  • /device exposes the SDR controls the Flex protocol has no verb for, including a setting's
    ArgInfo range.
  • RF gain is in dB, not 0..100 — AE sends dB, and rfgain_info is now answered.
  • A dropped stream restarts instead of going off the air, a device that stops producing samples
    without erroring is detected, and adapter.close() is bounded so a wedged driver cannot hold the
    exit open.
  • Radio dossiers: schema v0.1, a runtime loader (the IC-9700 adapter reads its facts from data),
    the IC-9700 and RTL-SDR v4 dossiers, and a validator. The gate is a pinned consumer of
    shack-experiments/radio-dossiers.

Project

CONSTITUTION.md and AGENTS.md write down the rules the project already ran on. CI now runs the
numpy suites and the __main__-less modules under pytest; segmentation is exercised on every
platform, with SIGTERM skipped where it cannot land.

Most of the levels and resolution work came in via #40, an RSPdx-R2 bring-up from @crypticpy.

Full diff: v0.4.3...v0.5.1

v0.4.3 — the update guard no longer trips over its own search

Choose a tag to compare

@nigelfenton nigelfenton released this 11 Aug 19:35
fb51dc2

A third fix for the one-click updater, found the same way as the other two: by running it on real
hardware rather than trusting the tests.

What was wrong

The "refuse to update while a gate is running" check searched the process list for aether_gate
but pgrep -af aether_gate returns anything that mentions the name: the shell running the
search, an editor with the file open, a tail on the log. So the guard fired with no gate running
at all
, refusing updates that should have been allowed.

That is the opposite of the v0.4.1 escape (which permitted updates it should have refused) and just
as useless: one lets an update run under a live radio, the other blocks updating forever.

It now requires an actual -m aether_gate module launch, and ignores -m aether_gate.setup (the
web UI itself) and any other submodule.

Also in 0.4.x

  • v0.4.2 — the update banner clears as soon as the install finishes, instead of continuing to
    offer an update that was already applied
  • v0.4.1 — the guard sees a gate started by systemd, not only one started from the page
  • v0.4.0 — real FM demodulation, S-meter, SDRplay support, the Pi appliance image, and
    one-click updating itself

All three updater bugs were invisible to the unit tests, which only exercised the paths the code
already knew about. Each now has a regression test that fails when the fix is removed.


Full changelog: v0.4.2...v0.4.3

v0.4.2 — the update banner clears when the update is done

Choose a tag to compare

@nigelfenton nigelfenton released this 11 Aug 19:31
3b006ca

A usability fix for the one-click updater. Everything else from v0.4.0 (real FM demodulation,
S-meter, SDRplay support, the Pi appliance image) is unchanged and described there.

What was wrong

After installing an update, the setup page kept offering the same update. It reported the
version it had imported when it started, not the one now on disk — so a freshly updated gate still
showed "Update available: v0.4.1 (you have 0.3.0)" until the web UI was restarted.

Nothing was broken by it (installing twice is idempotent), but for an operator who just pressed the
button and was told it worked, being told again that an update is available is exactly the kind of
confusion this feature exists to remove.

The page now reads the version from disk at request time, so the banner clears the moment the
install finishes:

You are up to date (0.4.2).

Also in this release

v0.4.1 — the update guard now sees a gate started by systemd, not only one started from the
setup page. On an appliance the gate normally runs as a service, so the "refuse while running"
check was not firing and an update could install underneath a live radio session. It now refuses
with HTTP 409 and names the right thing to stop.

Both were found the same way: by running the update for real on a Pi 4 rather than trusting the
tests. Every unit test passed throughout — they only exercised the paths the page already knew
about. Both now have regression tests that fail when the fix is removed.

Verified end-to-end on the appliance

A Pi 4 running 0.3.0, with the gate up as a systemd service:

install while running   -> HTTP 409, "stop it before updating"
stop, then install      -> "Updated to v0.4.2." (about 2 seconds)
banner immediately      -> "You are up to date (0.4.2)."   [no UI restart]
start the gate          -> running, streaming, previous version kept alongside

Full changelog: v0.4.0...v0.4.2

v0.4.1 — the update guard now sees a gate started by systemd

Choose a tag to compare

@nigelfenton nigelfenton released this 11 Aug 19:28
8227d63

A safety fix for the one-click updater introduced in v0.4.0. If you are on 0.4.0, take this one;
everything else in v0.4.0 (real FM demodulation, S-meter, SDRplay support, the Pi image) is
unchanged and described there.

What was wrong

The updater is supposed to refuse to install while a gate is running — swapping the code under a
live radio session is exactly the surprise the feature exists to avoid. It only checked for a gate
that the setup page itself had started.

On an appliance the gate normally runs as a systemd service (aether-gate-9700.service and
friends), which the page did not start. So the check never fired, and an update could install
underneath a gate that was streaming to AetherSDR at the time.

Found by running the update for real on a Pi 4 rather than trusting the tests — every unit test
passed throughout, because they only ever exercised the path the page knew about.

What changed

The check now looks for any running gate, and says which one to stop:

A gate is running as a system service. Stop it before updating
(sudo systemctl stop aether-gate-*), then try again.

rather than "press Stop", which is useless advice for a service the operator never started from
that page.

Verified on the appliance: with a gate running as a service the install is refused (HTTP 409), and
the full path — Stop, Install, Start — then completes in about two seconds and leaves a working
gate with the previous version kept alongside.


Full changelog: v0.4.0...v0.4.1

v0.4.0 — FM that actually works, and one-click updates

Choose a tag to compare

@nigelfenton nigelfenton released this 11 Aug 19:15
40d9b59

Aether-gate presents any radio to AetherSDR as a Flex 6000. This release makes the SDR path
genuinely usable — FM actually works now — and adds one-click updating so the appliance can keep
itself current without a terminal.

If you are running 0.3.0, this is the one to take

FM was silently an SSB detector. The soapy adapter had no FM demodulator at all: selecting FM
ran the signal through the SSB path, which sounds wrong and cannot decode anything. 1200-baud AX.25
packet (AFSK inside FM) was impossible. Now there is a real NBFM discriminator.

Updating

New: the setup page offers updates itself. When a newer release exists, a banner appears at
http://<your-gate>:8730 with an Install update button. It downloads the release, swaps it in,
and — if the new version will not start — puts the working one back and tells you so. No SSH, no
commands.

It will not update while the gate is running; press Stop first, then Start afterwards.

Radio / SDR

  • Real NBFM demodulation — FM, and therefore FM packet, works
  • An S-meter — the adapter previously reported no signal level at all
  • SSB sideband selection fixed — USB and LSB were mathematically identical
    (real(conj(z)) == real(z)), so both sidebands folded together on every SSB signal since the
    adapter was written
  • The demodulator is no longer parked on the DC spike — offset tuning moves the hardware centre
    away from the signal you are listening to
  • The panadapter shows the right span — it was labelling full-rate data with whatever span
    AetherSDR asked for, compressing the display
  • Audio no longer starves at odd sample rates — floor decimation plus a fractional resampler
    (a rounding bug used to click every ~1.3 seconds regardless of band or mode)
  • Waterfall tiles encode VitaFrequency correctly for AetherSDR ≥ #4412

Performance

The audio path was over its real-time budget on a Pi, dropping IQ blocks and producing chopped
audio. Three measured optimisations, none of which change what comes out:

measured on a Pi 4
Panadapter binning 187× faster — it was consuming 82% of the engine loop
Decimating filters up to 6.1× — only the samples that survive decimation are computed
Mixer (NCO) — one exponential per block instead of one per sample

⚠️ Sample rate matters more than any of this. samp_rate // 24000 can land on a prime number,
which collapses the staged decimation into one huge filter: 2.000 MS/s is 3.6× more expensive
than 2.040 MS/s
for that reason alone. If you have a choice, use 2.040 MS/s.

This is better, not finished — on a Pi 4 at 2 MS/s the audio path still runs at roughly half the
rate it needs, so heavily loaded setups may still hear breakup.

Hardware and deployment

  • SDRplay RSP support (RSP1A/RSP1B/RSP2/RSPduo/RSPdx) with a pinned API 3.15.2
  • A publishable Pi appliance image — flash it and go
  • Assorted deploy fixes, and tcpdump baked in for diagnosis

Known issues

  • Offset tuning leaves a dead band of 0.25 × sample rate at the low edge of the panadapter —
    that part of the span was never sampled, and it paints as an empty rectangle. See #37.
  • On a Pi 3, audio breakup at 2 MS/s is expected; a Pi 4 is better but not clear.

Full changelog: v0.3.0...v0.4.0

v0.3.0 — HPSDR adapter (Radioberry/HL2), telemetry + SWR, bare-carrier guard

Choose a tag to compare

@nigelfenton nigelfenton released this 16 Jul 22:31
24eeb3e

FT8 decodes end-to-end through AetherSDR + WSJT-X on 14.074, off a Radioberry, through the gate's own DSP.

Added

  • HPSDR Protocol-1 adapter (--adapter hpsdr) — raw IQ from a Radioberry or Hermes-Lite 2, presented to AE as a Flex.
  • EP6 response telemetry — PA temp / fwd / rev / current + SWR, from C&C bytes we already receive. swr is None when unmeasurable, never a fake 1.0 — a board with no sensors reports has_sensors=False and AE's SWR meter is omitted rather than showing a false 'perfect match'.
  • Bare-carrier guard (IC-9700)key_tx() refuses in a digital mode when AE registered no dax_tx stream. 127 of 261 keys on 2026-07-15 were exactly that: keyed, transmitting nothing but carrier.
  • CHANGELOG.md and the first tests for the core FFT. 113 tests pass.

Fixed

  • iq_to_dbm subsampled before the FFT — aliasing, not decimation; ~61% of each 4096-sample block discarded. Now FFTs the whole block and bins by peak: +1.4–1.9 dB, and it affects every IQ adapter (soapy/RTL, HPSDR, kenwood, yaesu).
  • HPSDR IQ sideband inverted — confirmed on air against WWV 15 MHz off-centre.

Known limitations (read these)

  • HPSDR is RX-only by construction — no key_tx, so AE's MOX cannot reach the radio. TX targets the HL2, which has native fwd/rev/temp/current the Radioberry PA hat lacks. See docs/HPSDR_TX_PLAN.md.
  • set_span() is a no-op — AE's zoom does nothing; cause not understood.
  • A Radioberry PA hat without the preAmp board has no thermal or SWR protection at all (no MAX11613). has_sensors reports this honestly.
  • --samp-rate 250000 on the kenwood adapter measured worse and is unexplained. Reverted.
  • Why AE sometimes omits stream create type=dax_tx before keying is unknown. The guard makes the consequence non-radiating; it does not fix the cause.

Full detail in CHANGELOG.md.

🤖 Generated with Claude Code

Aether-gate v0.2.0

Aether-gate v0.2.0 Pre-release
Pre-release

Choose a tag to compare

@nigelfenton nigelfenton released this 16 Jul 01:57
6e17e85

Second release of Aether-gate — the universal radio bridge that presents any rig or SDR to AetherSDR as a Flex 6000. This release makes the IC-9700 a fully working two-way radio through AetherSDR, including real digital-mode TX.

Highlights

🎉 Real modulated AX.25 TX on the IC-9700

The gate now keys the rig and sends modem audio end-to-end — a real AX.25 packet connect to a live node has been made through the gate. Guarded PTT (arm-latch, in-band check for 2m/70cm, TX watchdog), AE MOX → real keying, and the DAX-TX audio path that actually modulates the carrier.

🩺 Deaf-scope freeze FIXED

The long-standing IC-9700 LAN scope stall (froze every ~60–100 s) is resolved by porting the SDR9700 (w5jwp, GPL-3.0) LAN transport wholesale — the root cause was single-int RX-sequence tracking vs. a full seq-map. Plus three transport correctness fixes (seq-recovery, retransmit parse, watchdog cadence) and the S-meter-poll-as-keepalive fix.

🛠️ CI-V settings read/write facility

A reusable accessor for the rig's SET-menu items (1A 05) over the live gate session — reads and writes rig settings without a competing login. It found and fixed a real bug on first use (LAN MOD Level = 0 → bare carrier), and now auto-sets a usable LAN MOD Level on connect so digital TX just works on a factory-fresh rig.

🔌 Robust connect behaviour

  • Busy-refuse: a second client no longer hangs the gate — it's refused with a clean disconnect instead of leaving AE waiting forever.
  • Radio-state wins on reconnect: the gate comes up where the rig actually is.
  • Phantom-pan fix (no more spurious second panadapter / slice flicker on connect).

📻 Accurate radio reporting

Real RF-power reporting (0–100 % level, and accurate forward-power watts via the Po meter), correct Flex↔CI-V mode mapping (incl. data variants), and IC-905 5.7 GHz band named 5cm to match AE's vocabulary.

Notes

  • Icom LAN rigs (IC-9700) need only numpy; SoapySDR/hamlib are optional for USB/CAT/dongle paths.
  • ⚠️ TX on non-Flex USB/CAT rigs (hamlib) is still cosmetic-only (no PTT wired) — the real TX path is the IC-9700 LAN one above.

Full commit log: v0.1.0...v0.2.0

Aether-gate v0.1.0 (pre-release)

Pre-release

Choose a tag to compare

@nigelfenton nigelfenton released this 10 Jul 15:08
f16e9cc

Aether-gate presents any radio to AetherSDR as a FlexRadio 6000, so AE keeps a single ingest boundary. The core (a vendored flex-sim engine) speaks Flex — SmartSDR discovery, FlexLib control, VITA-49 framing, slices/pans, audio — and each radio is a small RadioAdapter that supplies just the spectrum.

⚠️ Early / pre-release. First tagged version — functional and live-validated on real hardware, but the adapter set and TX story are still growing. Expect changes.

Working in v0.1.0

  • IC-9700 over LAN (RS-BA1) — bidirectional control, CI-V 27h band scope → panadapter/waterfall, and LAN RX audio. Live-validated on real hardware (2m/440/23cm).
  • Radio-declared bands — a gateway advertises its real bands (e.g. 2m,440,23cm) so AE's band menu matches the actual rig (pairs with AetherSDR's radio-declared-bands work).
  • Multi-vendor adapters — Icom (LAN + USB CI-V), Yaesu & Kenwood (hamlib CAT + an IF-tap SDR for spectrum, since those rigs have no scope over CAT), and SoapySDR for raw-IQ SDRs. A 17-radio starter registry across 3 vendors.
  • Built-in test bench — the sim adapter drives AE with flex-sim's synthetic patterns, no radio needed.
  • Web setup / launcher UI (:8730) — pick a radio type, fill connection details, Start. Per-gate signal-control panels too.
  • Runs headless as a service (Raspberry Pi appliance) or from the CLI on Linux/macOS/Windows.

Known limitations

  • TX on non-Flex rigs is not wired yet — adapters advertise capability but no PTT seam keys real hardware; treat TX as RX-only for now.
  • USB-transport Icom (7300 etc.) and several registry rows are documented starters, not all hardware-verified.

Pure Python (stdlib + numpy + pyserial where a rig needs CAT). GPL-3.0.