SoapySDR - Package Installation
The purpose of this tutorial is to get the Amarisoft SoapySDR support module and its example programs working on a machine as quickly as possible, starting from a packaged release instead of from source you have to understand first. You copy one archive to the target, unpack it, and run a single script. The script checks the machine, installs whatever is missing, puts the tree in '/root/soapy_project', builds every component, and then confirms that the SoapySDR runtime has actually registered the 'amarisoft' factory. Taking it off again is one more script.
What you have once it is installed:
- 'driver=amarisoft' working for every SoapySDR client on the machine - SoapySDRUtil, GNU Radio, GQRX or a program of your own - with no Amarisoft specific code in any of them.
- Example programs in C++, Python and Octave that stream from the card, and 'hellosoapy', which proves the installation against real hardware rather than against its own test data.
- The scripts that keep it going: 'healthcheck_sw.sh' to exercise the whole tree, 'make_tarball.sh' to package it for the next machine, and 'uninstall.sh' to remove it and prove it is gone.
Table of Contents
- SoapySDR - Package Installation
Introduction
A SoapySDR support module is only useful once it is on the machine that owns the radio, and getting it there is a different problem from writing it. The module links the Amarisoft 'libsdr' library and records that library's location as an RPATH at build time, and it is loaded only by a SoapySDR runtime whose ABI version matches the one it was compiled against, so a binary produced on one machine will generally neither resolve nor load on another. The package described here therefore ships source rather than binaries: a single directory tree, a little over two hundred kilobytes, holding the support module, two C++ example programs, the Python and Octave examples, the probe programs that document 'libsdr' behaviour below the SoapySDR layer, and the scripts that build, exercise and remove all of it. Installing is a matter of unpacking that tree on the target and running one script, which establishes whether a radio is live on the machine, installs whatever distribution packages are missing, puts the tree in its permanent location, compiles every component in the order they depend on each other, and then asks the SoapySDR runtime whether the factory actually registered - a question worth asking separately, because a module can compile and install successfully and still fail to load. The same tree carries the script that packages it for the next machine and the script that removes what was installed, so a release can be moved, rebuilt, verified and withdrawn without any step being done by hand. This tutorial covers that lifecycle on a Fedora based Amarisoft callbox or UE simulator, including the deliberate separation between the unpacked release and the working tree that keeps your own files intact across upgrades, and the one class of dependency that no script can install for you: the licensed Amarisoft trx_sdr SDK and the 'sdr' kernel module that creates the '/dev/sdrN' nodes.
-
Context and Background
- The support module, 'libamarisoftSupport.so', is what allows any SoapySDR application to drive an Amarisoft SDR card without containing Amarisoft specific code. How it is written and validated is the subject of the companion SoapySDR tutorial; this one is about moving it between machines and keeping it installed.
- The package holds source only and no build output, for two reasons that both point the same way: the module records the path of the 'libsdr' it links against, and it is loaded only by a SoapySDR runtime of matching ABI. Shipping source and compiling in place removes both problems at once.
- Three kinds of dependency are involved. Distribution packages - SoapySDR and its headers, a C and C++ toolchain, cmake, make, the python3 modules and, optionally, Octave - can be installed automatically. The licensed Amarisoft trx_sdr SDK cannot, because it is not in any repository. Nor can the 'sdr' kernel module, which is part of the Amarisoft installation and is what creates '/dev/sdrN'.
- The archive unpacks into a directory named after the release itself, while the tree is built and kept in '/root/soapy_project', a real directory that the installer copies each release into. The unpacked release is the delivery; the working tree is where you live.
-
Relevance and Importance of the Tutorial
- A test bench is rarely one machine. Cards are moved, callboxes are re-imaged, and a module built on the wrong host fails in ways that read badly - a factory that is not registered, or a library that cannot be found - long after the mistake was made. Building on the machine that will run it is the rule the whole procedure is arranged around.
- Installing under a live radio can disturb it. A package transaction can pull a library out from under a running 'lteenb', and the rebuilt module is copied over the installed one in place rather than atomically, so a process holding it mapped can fault mid-run. Knowing that these checks exist, and when it is legitimate to override them, is what keeps an installation from interrupting a cell that somebody else is using.
- Keeping one working tree that survives upgrades matters in practice: captures, notes, configuration and local scripts accumulate in it, and an upgrade that silently hid them would cost more than it saved.
- Being able to remove the module and prove that it is gone is equally practical, whether a machine is being handed back, or a fault has to be isolated to this software rather than to the radio.
-
Prerequisite Knowledge and Skills
- Ordinary Linux command line work: unpacking a tarball, copying files with 'scp', running shell scripts as root, and reading what a package manager reports.
- Familiarity with the Amarisoft installation layout - '/root/trx_sdr', the 'sdr' kernel module, the '/dev/sdrN' nodes - and with stopping and starting the 'lte' service.
- Enough build awareness to read a CMake or compiler failure and tell a missing header from a missing library. No programming is required to follow the tutorial itself.
- Root access on the target machine, and an Amarisoft Extranet account for the licensed trx_sdr release that the module is compiled against.
-
Learning Outcomes
- Deploy the package to a machine that has never seen it, and to one already carrying an older release, without losing anything that was left in the working tree.
- Choose the right mode for the situation: reporting only, building without touching the package manager, leaving Octave out, or proceeding despite a live radio, and redirecting the SDK path or the install location when the defaults do not apply.
- Read the installer's output well enough to separate a missing package from a missing SDK from a module that built but did not register, and know which log to open when a build fails.
- Confirm an installation against real hardware, and drive the whole procedure with a coding agent while knowing what to check afterwards.
- Remove the installed module, verify the removal from the runtime's point of view rather than the filesystem's, and understand why the packages and the tree itself are deliberately left behind.
Summary of the Tutorial
This tutorial covers the whole deployment lifecycle of the 'soapy_project' package: unpacking it on a target machine, installing and building it there, proving the result against real hardware, packaging it again for the next machine, and removing it. Every listing shown on the page is the output of a real run on a bench machine rather than an illustration, including the ones where something is missing.
-
Package and Layout:
- The archive is source only, about two hundred kilobytes, and unpacks into a directory named after the release - 'soapy-project-pack-YYYYMMDD' - so that unpacking one release can never write into another, or into the tree being worked in.
- The working tree is '/root/soapy_project', a real directory. Installing copies the release into it: the files the package carries are replaced, and anything else in there is left untouched, which is what allows captures, notes and local programs to survive an upgrade.
- Build directories and compiled artefacts are deliberately excluded from the package, because CMake records absolute paths in its cache and 'octave/build/soapy.oct' carries the ABI of the machine that built it.
-
Manual Installation:
- Copy the archive to '/root', unpack it with 'tar xzf ... -C /root', enter the unpacked directory and run './install.sh'. Everything after that happens in '/root/soapy_project'.
- The script reports the state of the bench, the dependencies, the SoapySDR ABI, the Amarisoft components, where the tree is going, the build of each component, and finally whether the runtime registered the 'amarisoft' factory.
- Run it again from '/root/soapy_project' itself and it recognises that it is already there, copies nothing and rebuilds in place - the ordinary way to rebuild after editing something in the tree.
- A machine still carrying the older symlink layout is converted: the link is replaced by a real directory, and the release it pointed at is left alone.
-
Install Modes and Safety Checks:
- '--check' reports and changes nothing, and runs to the end even on a machine that has none of the dependencies. '--no-deps' builds with what is installed and never calls the package manager, stopping only if something the build genuinely needs is absent. '--no-octave' leaves out the one large dependency, which costs roughly 750 MB. '--ignore-running' proceeds despite a live radio.
- 'TRX_SDR_ROOT' points at a specific trx_sdr release; 'SOAPY_PROJECT_DIR' puts the working tree somewhere other than '/root/soapy_project'.
- Before any of it, the script looks for an active lte service, for 'lteenb', 'lteue', 'ltemme' or 'ltembms', for any process holding a '/dev/sdrN' node, and for any process with the installed module mapped. Any of them stops the run and names what to stop.
- Build failures leave their logs in '/tmp/build_*.log', and the last lines are printed on the spot.
-
Verification Against Hardware:
- 'hellosoapy' exercises the module through the public SoapySDR API alone - eleven numbered checks covering enumeration, information, streaming, retuning and the out of range guard. The transmit and loopback group runs only when '--tx' is given, because those checks key the TX port.
- A board can be pinned by passing '/dev/sdrN', which is how a check is kept off a card somebody else is using.
- 'healthcheck_sw.sh' goes further, building and exercising every program in the tree, sweeping every sample rate the board reports, and asserting that the three probe programs meant to die do die.
-
Packaging for the Next Machine:
- 'make_tarball.sh' packs the working tree into a dated archive and prints the exact deploy commands for it, with the real names filled in.
- A second pack on the same day takes a numbered suffix, and the number only ever counts upward, so an archive never inherits the name of one that was deleted locally and may still exist elsewhere.
-
Install with AI Agent:
- The scripts are written so that a coding agent with shell access can carry out the procedure unattended: every failure names the thing to fix, every step has a non-destructive form, and every script verifies its own result independently of the step that produced it.
- The same properties make diagnosis possible after the fact, since the build logs remain and individual checks can be re-run.
- An agent installing software still modifies the machine, so a specific target, a '--check' first, and an awareness that packages will be installed are all worth insisting on.
-
Uninstalling the Package:
- 'install.sh' puts exactly one file on the system, the support module, and './uninstall.sh' removes that one file by default; '--check' lists it without removing anything, and '--build' or '--all' also deletes the build directories.
- The path removed is taken from the 'install_manifest.txt' that CMake wrote at install time, with a search of the SoapySDR module directories as a fallback when the build tree is already gone.
- Packages are never removed - other software is likely to depend on them, and removing the SoapySDR runtime would take the whole module system with it. The working tree is left alone too, because what is in it is yours.
- The script finishes by asking SoapySDR whether the factory is still registered, which confirms the removal from the runtime's point of view rather than the filesystem's.
-
Using the Module from an Application:
- An application opens the card like any other SoapySDR device; the only Amarisoft specific element is 'driver=amarisoft', with an optional 'device' argument naming the board.
- Because SoapySDR device arguments are themselves key and value pairs, the 'libsdr' multi-board syntax composes naturally, and several construction time arguments exist so that a device can be configured from a single string - which is what applications offering only one device field require.
- At run time the settings interface exposes the hardware specific knobs, notably 'auto_restart' and 'tx_max_lead_us'.
-
Supported Capabilities:
- Two RX and two TX channels per board in full duplex, CF32 and CS16 stream formats, 300 MHz to 6 GHz accepted, sample rates from 1 MHz to 122.88 MHz on the n x 1.92 MHz ladder, and AGC on receive.
- Gain, antenna, clock and time source selection, and the sensors - FPGA temperature, overflow and underflow counters, absolute power, GPS lock - are all reachable through the standard calls.
- The gain and bandwidth figures are read from the board at run time and agree with the published trx_sdr documentation; the frequency range is the one place where what the driver accepts is wider than what is specified.
-
Limitations:
- One stream per direction, and a single RF port. Timed and burst limited 'activateStream' are not supported, and 'setHardwareTime' is not implemented because the sample counter belongs to 'libsdr'.
- The register, GPIO, I2C, SPI and UART interfaces are absent, since 'libsdr' exposes no equivalent.
- The frequency and gain constants are AD9361 values measured on one board, and timestamps restart on every hardware restart, which includes every retune.
-
Directory Structure:
- Where the unpacked release, the working tree, the installed module, the SoapySDR files and the Amarisoft SDK each sit, and which of them are mandatory at run time.
- Anything added to the working tree by hand is part of it and is not touched by a later install.
Installing/Uninstalling soapy_project package
Everything described in this tutorial - the support module, the two C++ programs, the Python and Octave examples and the probe programs - lives in a single directory tree called 'soapy_project'. This section covers moving that tree to another machine, building it there, and removing it again afterwards.
One rule governs the whole process: build on the machine that will run it. The support module links the Amarisoft libsdr and records that library's location as an RPATH at build time, so a binary compiled elsewhere will not resolve unless trx_sdr happens to sit at the identical path. The module is also loaded only by a SoapySDR runtime with a matching ABI. Both problems disappear if the source is shipped and compiled in place, which is what the scripts below do.
Manual Installation
Download the package here and copy it into '/root' on the machine that will run it. The archive is small, about two hundred kilobytes, because it holds source only and no build output. It expands into a single directory named after the archive itself, 'soapy-project-pack-xxxxxx', rather than into one called 'soapy_project'.
That naming is deliberate. The unpacked directory is the delivery, not the working tree: unpacking can never write into a release that is already there, and never into the tree you work in. Unpacking an archive over an existing directory merges the two rather than replacing it - tar overwrites the files it carries, leaves everything else where it was, and deletes nothing - so a shared name would produce a tree that is part one release and part another.
The working tree is '/root/soapy_project', and it is a real directory. Installing copies the release into it. The files the package carries are replaced; everything else in there is left exactly as it was, which is the whole point of copying rather than pointing a symlink at the unpacked release. A capture you left in the tree, a note, a script of your own, an example you edited - all of it is still there after the next release is installed. A symlink cannot do that: re-pointing it at a new release does not delete the old tree, but everything in it disappears from '/root/soapy_project', because that name now means somewhere else.
The fixed location matters for a second reason as well. '/root/soapy_project' is the path in the tutorials, in the Octave 'addpath' line and in every example command, and each build directory records its own absolute path inside CMakeCache.txt. Keeping one real directory keeps all of that true, and keeps one set of build directories rather than one per release.
|
[root@target ~]# cd /root [root@target ~]# tar xzf soapy-project-pack-20260921.tar.gz -C /root //the package file name varies with the release [root@target ~]# cd soapy-project-pack-20260921 [root@target soapy-project-pack-20260921]# ./install.sh |
That is the whole deployment: unpack, enter the unpacked directory, run the script. Everything after that happens in '/root/soapy_project' - the copy, the build and the install - and the unpacked release can be deleted once it is in, or kept as a record of what was shipped.
'install.sh' establishes first whether a radio is live on the machine, then checks every dependency and installs the ones that are missing, reports where the tree is going and copies it there, builds everything that has something to compile - the support module, the two C++ programs, the Octave binding and the probe programs, in the order they depend on each other - and finishes by confirming that the SoapySDR runtime has actually registered the factory. That last step matters: a module can compile and install successfully and still fail to load, and the build reports success either way.
Run it a second time from '/root/soapy_project' itself and it recognises that it is already there, copies nothing, and rebuilds in place. That is the ordinary way to rebuild after editing something in the tree. A machine still carrying the older layout, where '/root/soapy_project' is a symlink to an unpacked release, is handled too: the link is replaced by a real directory holding this release, and the directory it pointed at is left alone, so anything in it can still be fetched afterwards.
The state of the bench is established before anything else, because two things here can disturb a running radio. A package transaction can pull a library out from under a live 'lteenb', and 'make install' copies the rebuilt module over the installed one in place rather than atomically, so a process holding it mapped can fault in the middle of a run. The script looks for an active lte service, for 'lteenb', 'lteue', 'ltemme' or 'ltembms', for any process holding a '/dev/sdrN' node, and for any process with the installed module mapped; any of those stops the run before the package manager is called, and names what to stop and how. '--ignore-running' overrides it, and '--check' reports the same state and carries on, since it changes nothing.
|
[root@target soapy_project]# ./install.sh //rebuilding in place: no copy, this is already the tree |
Several other modes are available. '--check' reports what is missing and stops, installing and building nothing, which is the safe way to find out whether a machine is ready; it runs to the end even on a machine that has none of the dependencies yet. '--no-deps' never calls the package manager: it builds with what is already installed, and stops only when something the build genuinely needs is absent - Python and Octave are treated as optional, because the build skips what they would have produced rather than failing. '--no-octave' leaves Octave out altogether, which is worth knowing because it is the one large dependency here: one package carries both front ends, pulls in a TeX installation, and costs roughly 750 MB.
|
./install.sh --check # report only; install and build nothing ./install.sh --no-deps # build with what is here; never call the package manager ./install.sh --no-octave # skip octave/ and its ~750 MB of packages ./install.sh --ignore-running # install even though a radio is live on this machine
# if trx_sdr is not at the default /root/trx_sdr: TRX_SDR_ROOT=/root/trx_sdr-linux-2026-08-18 ./install.sh
# to keep the tree somewhere other than /root/soapy_project: SOAPY_PROJECT_DIR=/opt/soapy_project ./install.sh |
The output below is a real deployment of this release onto a second machine, which differs from the development machine in several ways at once - half the cores, two SDR boards rather than four, and no Octave installed at all - and none of it needed adjusting for. It was run with '--no-deps', so the package manager was never called: Octave is the only thing missing there, it is optional, and the run says so and carries on to build everything else. That machine already had a '/root/soapy_project' from an earlier release, so the run reports that it is copying over it; on a machine that has never had one, the same line reads 'will be created'.
=== bench state ===
[ ok ] nothing running: no lte process, no /dev/sdrN held, module not in use
=== dependencies ===
[ ok ] SoapySDR runtime
[ ok ] SoapySDR headers
[ ok ] cmake
[ ok ] C++ compiler
[ ok ] C compiler (for probe/)
[ ok ] make
[ ok ] python3 interpreter (for python/)
[ ok ] python3 SoapySDR bindings (for python/)
[ ok ] python3 numpy (for python/)
[ ok ] python3 matplotlib (for python/plot_spectrum.py)
[ -- ] octave-cli, terminal front end (for octave/) (package: octave)
[ -- ] octave --gui, windowed front end (package: octave)
[ -- ] mkoctfile, to build octave/ (for octave/) (package: octave-devel)
missing: octave octave-devel
--no-deps was given, so nothing will be installed. None of these
is needed to build the tree; whatever uses them is skipped below.
=== SoapySDR version ===
[ ok ] API v0.7.1, ABI v0.7 (the version this tree was tested against)
=== Amarisoft components ===
[ ok ] trx_sdr SDK at /root/trx_sdr
[ ok ] device nodes: /dev/sdr0 /dev/sdr1
=== install location ===
[ ok ] /root/soapy_project exists - this release will be copied over it
The files this package carries are replaced. Anything else
in there is left alone.
=== installing the tree into /root/soapy_project ===
[ ok ] copied from /root/soapy-project-pack-20260921-5
=== building ===
SoapyAmarisoft ok
hellosoapy ok
detectssb ok
octave skipped (mkoctfile not installed)
probe ok (8 programs)
python ok (8 programs)
=== verify ===
Module found: /usr/local/lib64/SoapySDR/modules0.7/libamarisoftSupport.so
Available factories... amarisoft
|
Basic Operation Check
The obvious next step is to prove the installation against real hardware, which is what 'hellosoapy' is for. On that same machine, pinned to the second board because the first was in use, it passed nine checks and skipped the transmit group - those checks key the TX port, and are run only when '--tx' is given.
|
[root@target soapy_project]# ./hellosoapy/build/hellosoapy /dev/sdr1 |
-> PASS: 1. Module loaded and factory registered -> PASS: 2. Device enumeration ... tuned to 2140 MHz in 141.766 ms | 6144000 samples | mean -52.5356 dBFS -> PASS: 8. Retune while streaming -- SKIP: TX and loopback (pass --tx to include them; they key the TX port) -> PASS: 11. Out-of-range frequency guard ============================================================ passed: 9 failed: 0 skipped: 1 device: /dev/sdr1 ============================================================ |
The package was produced from a working tree by 'make_tarball.sh', which is included in it. If you modify the sources and want to move them to another machine, run that script and a dated archive appears alongside them, named 'soapy-project-pack-YYYYMMDD.tar.gz' and carrying a top level directory of that same name. A second pack on the same day gets a '-2' suffix rather than quietly taking the first one's name, since the name is also the directory it will unpack into on the target. The script finishes by printing the exact deploy commands for the pack it has just built - the 'scp', the 'tar', the 'cd' and the './install.sh' - with the real names filled in. The suffix only ever counts upward, so a pack never inherits the name of one that has been deleted here and may still be in somebody else's hands.
It excludes the build directories deliberately, because CMake writes absolute paths into its cache and a build tree carried to a different location refers to directories that no longer exist, failing in a way that is hard to read. The compiled artefacts are left out for the same reason: 'octave/build/soapy.oct' carries both the Octave and the SoapySDR ABI of the machine that built it and is loaded only by a matching pair, so it is rebuilt on the target rather than shipped.
Basic Health Check
What I wants to recommend to do as next step is try basic health check.
'hellosoapy' proves the module against the hardware; 'healthcheck_sw.sh' goes one step further and checks every program in the tree. It builds anything that is not yet built, then runs each program the way it is meant to be run and checks that it exits the way it is supposed to. It answers the question "does the software still work", not "is there a signal on the air", so nothing needs to be transmitting: the SSB detectors are expected to find nothing on a bare bench, and that counts as healthy. Run it from the working tree:
|
[root@target ~]# cd /root/soapy_project [root@target soapy_project]# ./healthcheck_sw.sh |
Without arguments the script picks the first board that no other process has open. If every board is busy, for example because a gNB is running on it, it stops and says so rather than interfering. The following options change what it does:
|
./healthcheck_sw.sh # build if needed, then check everything ./healthcheck_sw.sh --quick # only what needs no radio ./healthcheck_sw.sh --tx # also run the checks that key the TX port ./healthcheck_sw.sh --no-build # check what is already built; build nothing ./healthcheck_sw.sh /dev/sdr1 # pin a board instead of auto-selecting |
The run below is from a machine with two AD9361 boards, both free, so '/dev/sdr0' was selected. The checks are grouped in the order they run: the build, the environment (SoapySDR finds the module, the Python and Octave bindings load), checks that need no radio (help text, DSP self tests), input validation (every program must reject a bad argument rather than hang or crash), and then the live checks on the board. The live part streams at every sample rate the board reports, runs the C++, Python and Octave programs against real hardware, and runs the 'probe' programs that pin down 'libsdr' behaviour. The whole run took just over a minute.
--- build --- PASS build SoapyAmarisoft PASS build hellosoapy PASS build detectssb PASS build probe/*.c 8 programs --- environment --- PASS SoapySDR sees the module PASS python3 SoapySDR + numpy PASS python scripts parse PASS octave loads the soapy binding --- self contained checks (no radio) --- PASS hellosoapy --help PASS detectssb --self-test (DSP) ... PASS gps_print.py --self-test --- input validation (must reject, never hang or crash) --- PASS hellosoapy rejects --rate 25e6 PASS hellosoapy rejects --rate 500e6 ... PASS detect_ssb.m requires --arfcn Using device: /dev/sdr0 board rates : 1.92 3.84 7.68 15.36 23.04 30.72 61.44 MHz using : low=1.92 mid=15.36 scs=30 reconfigure : ~1s per stop/start cycle (measured) live checks : roughly 4m22s (~82 reconfigures) --- module test suite --- PASS SoapyAmarisoft run_tests.sh --- hellosoapy --- PASS hellosoapy RX suite PASS hellosoapy at 1.92 Msps --- sample rate sweep (every rate this board reports) --- board reports: 1.92 3.84 7.68 15.36 23.04 30.72 61.44 MHz PASS RX at 1.92 Msps ... PASS RX at 61.44 Msps --- detectssb (live) --- PASS detectssb live RX --- python (live) --- PASS rx_minimal.py PASS detect_ssb.py PASS detect_ssb_fast.py PASS plot_spectrum.py --- octave (live) --- PASS rx_minimal.m PASS detect_ssb.m --- probe programs --- PASS probe (libsdr basics) PASS probe2 (start params) PASS probe4 (min read count) PASS probe5 (channel counts) PASS probe6 (5 start/stop cycles) --- probe programs that fail on purpose --- PASS probe3 (invalid freq is fatal in libsdr) died as designed (exit 1) PASS probe7 (dump_info before start segfaults) died as designed (exit 139) PASS probe8 (gain limits before start segfault) died as designed (exit 139) ============================================================ passed: 59 failed: 0 skipped: 0 device: /dev/sdr0 detail: /root/soapy_project/healthcheck_sw.log ============================================================ |
The live checks are timed from the board itself. Before they start, the script measures how long one stop and restart of the board takes and sets every timeout from that figure. This matters because the boards differ widely: an AD9361 board does it in about a second, as here, while an SDR100 runs a full AD9371 initialisation each time and takes about 32 seconds. Fixed timeouts that suit one board would make the other look as if it had hung. The time estimate printed under 'live checks' is an upper bound built from that measurement, which is why this run finished well inside it.
Three results in the last group are crashes, and they are reported as PASS on purpose. 'probe3' tunes to an out-of-range frequency to show that 'libsdr' ends the process, and 'probe7' and 'probe8' call 'libsdr' functions before the board is started to show that they segfault. These are the 'libsdr' limits the module guards against, so a clean exit from one of them would mean the limit has changed, and that is reported as a failure.
The script exits with status 0 only when nothing failed, so it can be used in scripts as well as by hand. The full output of every check, including the build logs, is written to '/root/soapy_project/healthcheck_sw.log'. Look there first when a check fails.
Install with AI Agent
The scripts were written so that a coding agent with shell access can carry out the whole procedure without further instruction, which is worth mentioning because it is how this package was developed and tested in the first place. Given the tarball and an SSH login, an agent such as Claude Code can copy it across, install it, run the checks and report back.
|
Copy soapy-project-pack-20260921.tar.gz to root@aaa.bbb.ccc.ddd (password: ****), unpack it under /root, run ./install.sh from the unpacked directory, and run hellosoapy on /dev/sdr1 to confirm it works. /dev/sdr0 is in use. |
Three properties of the scripts make this work reliably rather than by luck. Every failure message names the thing to fix rather than merely reporting that something went wrong, so an agent can act on it instead of guessing. Every step has a non-destructive form - 'install.sh --check' and 'uninstall.sh --check' - which lets an agent establish the state of a machine before changing it. And every script ends by verifying the result independently of the step that produced it, so success is confirmed rather than assumed.
The same properties help a human, and the workflow is worth using in reverse as well: when something does go wrong, the build logs are left in '/tmp/build_*.log' and the checks can be re-run individually, which gives an agent enough to diagnose the problem rather than simply reporting a failure.
Uninstalling the package
'uninstall.sh' reverses what 'install.sh' did. 'install.sh' puts exactly one file on the system - the support module - and by default that one file is all this removes. The build directories stay unless '--build' or '--all' asks for them, and the distribution packages are never removed at all.
|
[root@target ~]# cd /root/soapy_project [root@target soapy_project]# ./uninstall.sh |
=== installed module === /usr/local/lib64/SoapySDR/modules0.7/libamarisoftSupport.so removed /usr/local/lib64/SoapySDR/modules0.7/libamarisoftSupport.so === verify === SoapySDR no longer lists an amarisoft factory clean Reinstall with: ./install.sh |
The file to remove is taken from 'install_manifest.txt', which CMake writes at install time listing exactly what it placed on the system. That is more dependable than guessing a path, and it is why the build directories are removed last rather than first. If the build tree has already been deleted the script falls back to searching the SoapySDR module directories by name.
Packages are deliberately not touched, and there is no option to remove them. Other software on the machine is likely to depend on the same ones, and removing the SoapySDR runtime in particular would take the whole module system with it, including modules this package never installed. Anything 'install.sh' added - SoapySDR, SoapySDR-devel, cmake, gcc-c++, make, the python3 modules, Octave - has to be removed by hand if that is really wanted.
|
./uninstall.sh --check # list what would be removed, remove nothing ./uninstall.sh --build # also delete the build/ directories ./uninstall.sh --all # module and build directories together
# the tree itself is never removed by the script, because what is in it # is yours. To remove it, and the unpacked release it came from: rm -rf /root/soapy_project rm -rf /root/soapy-project-pack-YYYYMMDD |
The script finishes by asking SoapySDR whether the factory is still registered, which is the same check the installation used and confirms the removal from the runtime's point of view rather than the filesystem's. If a copy of the module survives somewhere else on the search path, this is where it shows up.
Using the Module from an Application
Once the module is installed, an application opens the card the same way it would open any other SoapySDR device. The only Amarisoft specific element is the 'driver=amarisoft' argument, and optionally a 'device' argument naming which board to use. If 'device' is omitted, the first '/dev/sdrN' node found is used.
|
SoapySDR::Kwargs args; args["driver"] = "amarisoft"; args["device"] = "/dev/sdr2"; auto *dev = SoapySDR::Device::make(args);
dev->setSampleRate(SOAPY_SDR_RX, 0, 23.04e6); dev->setFrequency (SOAPY_SDR_RX, 0, 2140e6); dev->setGain (SOAPY_SDR_RX, 0, 40);
auto *rx = dev->setupStream(SOAPY_SDR_RX, SOAPY_SDR_CF32, {0}); dev->activateStream(rx);
std::vector<std::complex<float>> buf(dev->getStreamMTU(rx)); void *buffs[] = { buf.data() }; int flags; long long timeNs; int n = dev->readStream(rx, buffs, buf.size(), flags, timeNs, 1000000); |
Because SoapySDR device arguments are themselves comma separated key and value pairs, the 'libsdr' multi-board syntax composes naturally. Passing 'dev0' and 'dev1' opens two boards as a single synchronised device, exactly as the underlying library expects.
|
driver=amarisoft,dev0=/dev/sdr0,dev1=/dev/sdr1 |
The device arguments below are recognised at construction time. Most of them mirror a normal setter and exist so that a device can be fully configured from a single argument string, which is convenient for applications such as GQRX that only offer one device string field.
|
device / path / node single board to open (default: first found) dev0, dev1, ... multi-board form passed to msdr_open rx_channels / tx_channels channels advertised (default 2 / 2) probe_info allow a brief start to read board identity sample_rate, frequency, rx_freq, tx_freq, rx_gain, tx_gain, bandwidth, rx_antenna, clock_source, time_source, log_level |
At run time, the settings interface exposes the knobs specific to this hardware. They are listed by 'SoapySDRUtil --probe' and can be changed with 'writeSetting'. The two most useful are 'auto_restart', which controls whether a configuration change while streaming triggers an immediate restart, and 'tx_max_lead_us', which controls the transmit throttle described earlier.
Supported Capabilities
|
Driver key Hardware key AD9361 Channels 2 RX, 2 TX per board, full duplex Stream formats CF32 (native, full scale 1.0), CS16 Frequency range 300 MHz .. 6 GHz accepted (specified: 500 / 400 MHz up) Sample rate 1 MHz .. 122.88 MHz continuous; ladder n x 1.92 MHz, n = 1,2,3,6,9,12,16,18,24,32 RX gain 3 .. 71 dB below 4 GHz, 0 .. 62 dB above; AGC supported TX gain 0 .. 89.75 dB Antennas RX: RX, TX_RX TX: TX Clock sources internal, external, external_10mhz Time sources none, internal, gps, external, ptm, external_50 Sensors fpga_temp, rx_overflow_count, tx_underflow_count, abs_rx_power, abs_tx_power, gps_locked, info |
The gain and bandwidth figures above are read from the board at run time and agree exactly with the values published in trx_sdr.doc: 0 to 89.75 dB of transmit gain, 3 to 71 dB of receive gain below 4 GHz narrowing to 0 to 62 dB above it, and an analogue bandwidth from under 200 kHz to 56 MHz. The frequency range is the one exception, and is discussed above: the figure quoted here is what the driver accepts, which is wider than the coverage specified in trx_sdr.doc and trx_sdr100.doc.
Limitations
The following limits are inherent to the current implementation or to the underlying library. They are listed here so that they are discovered before rather than during an experiment.
- One stream per direction. 'libsdr' keeps a single read and write cursor per RF port, so two concurrent readers would steal each other's samples. 'setupStream' rejects the second one with a clear error rather than allowing silent corruption.
- A single RF port ('rf_port 0') is configured. Multi-port CPRI setups are outside the scope of this module.
- Timed and burst limited 'activateStream' are not supported. Receive starts immediately and runs until 'deactivateStream'. A warning is logged if a timestamp or burst count is supplied.
- 'setHardwareTime' is not implemented, because the sample counter is owned by 'libsdr' and cannot be set from the API.
- The register, GPIO, I2C, SPI and UART interfaces are not implemented, since 'libsdr' exposes no equivalent.
- The frequency and gain limits are AD9361 values measured on one board. Other Amarisoft front ends, such as AD937x or a CPRI connected RRH, will need the constants in 'SoapyAmarisoft.hpp' revisited.
- Timestamps restart whenever the hardware is restarted, which includes every retune. They are monotonic only within a single activation.
Directory Structure
The following summarises where everything ends up after a successful build and install, and which files are mandatory at run time.
|
## 1. Project source tree
/root/soapy-project-pack-20260921-5/ the unpacked release: the delivery, which install.sh copies from and which can be deleted afterwards
/root/soapy_project/ the working tree: a real directory, where everything below is built and kept | +-- install.sh, uninstall.sh install and remove the module | +-- make_tarball.sh pack this tree for another machine | +-- healthcheck_sw.sh build and exercise everything | +-- SoapyAmarisoft/ the support module, detailed below | +-- hellosoapy/ hardware checks over the public API | +-- detectssb/ real time SSB detector | +-- python/ python examples | +-- octave/ octave binding and examples | +-- probe/ libsdr behaviour probes | +-- anything you put here yours - captures, notes, your own | programs. install.sh does not touch it
/root/soapy_project/SoapyAmarisoft/ | +-- CMakeLists.txt [MANDATORY] build definition | +-- SoapyAmarisoft.hpp [MANDATORY] class + HW constants | +-- Registration.cpp [MANDATORY] factory registration | +-- Settings.cpp [MANDATORY] control plane | +-- Streaming.cpp [MANDATORY] data plane | +-- build.sh, run_tests.sh helper scripts | +-- README.md design + measured HW behaviour | +-- tests/soapy_amarisoft_test.cpp test tool source | +-- build/ created by cmake | +-- libamarisoftSupport.so the module (before install) | +-- tests/soapy_amarisoft_test test tool binary
## 2. Installed module
/usr/local/lib64/SoapySDR/modules0.7/ +-- libamarisoftSupport.so [MANDATORY] scanned by SoapySDR at library load time
## 3. SoapySDR files (from SoapySDR / SoapySDR-devel)
/usr/include/SoapySDR/ [MANDATORY] headers to build against /usr/lib64/libSoapySDR.so [MANDATORY] runtime library /usr/share/cmake/SoapySDR/ [MANDATORY] SOAPY_SDR_MODULE_UTIL macro
## 4. Amarisoft SDK files required to build and run
/root/trx_sdr/ symlink to the trx_sdr release | +-- api/libsdr.h [MANDATORY] msdr_* API declarations | +-- api/libsdr.so [MANDATORY] linked, and found via RPATH | +-- kernel/flags.h [MANDATORY] included by libsdr.h
/dev/sdr0 .. /dev/sdr3 [MANDATORY] created by the sdr kernel module (lsmod | grep sdr) a board already opened by lteue or lteenb cannot be opened by SoapySDR |