/api/v1/radio/frequencySet frequency#
Request body application/jsonrequired
center_frequency_hz requiredinteger · int64
channelstring
Which side to tune: "rx", "tx" or "both" (default).
RX AND TX ARE INDEPENDENT LOs. Measured on this bench 2026-09-12: the RX LO sat at 2.400 GHz while the TX LO was still at its 2.450 GHz default, so an analyser on TX1A at 2.4 GHz saw nothing and the API reported one "center_frequency_hz" that described neither.
tune_lo boolean
true (default) also drives the AD9361's analogue LO. false writes
only the PL's digital NCO (chan_cfg::FREQ_HZ).
The distinction is not pedantry: until today this route wrote ONLY the
NCO, so /radio/status reported a centre frequency the transmitter had
never been told about.
Responses
200OKapplication/json
rx_state requiredstring
tx_state requiredstring
center_frequency_hz requiredinteger · int64
rx_gain_db requiredinteger · int32
tx_gain_db requiredinteger · int32
loopbackrequiredboolean
timing_lock requiredboolean
carrier_lock requiredboolean
rx_lo_hz integer · int64 · nullable
rx_nco_offset_hz requiredinteger · int64
The DDC's digital offset from the RX LO, signed, in Hz.
tx_nco_offset_hz requiredinteger · int64
The DUC's digital offset from the TX LO, signed, in Hz.
profile_center_frequency_hz requiredinteger · int64
What the applied PROFILE declares as its centre, read from the inert
chan_cfg shadow. It describes the configuration, not the radio, and
the two diverge the moment anyone tunes by hand.
tx_lo_hz integer · int64 · nullable
rx_rf_gain_db number · double · nullable
The part's own gain, in dB at the connector. TX is an ATTENUATION.
tx_rf_gain_db number · double · nullable
rx_bandwidth_hz integer · int64 · nullable
The part's ANALOG filter width, per direction, read from IIO.
NOT a PL register — the AD9361 has none in this address space, which is
exactly why the datapath diagram could not show it. None means the
attribute could not be read, never a default: this is the value that sat
at 18 MHz for a 53 kHz signal (STE-998), and a plausible-looking number
here would be worse than an absence.
The part QUANTISES what is written, so this read-back legitimately differs from any requested width. It is the truth; the request is not.
SERIALISED EVEN WHEN NULL, unlike its neighbours above, and the difference
is deliberate. An ABSENT field cannot be told apart from an older daemon
that never had it; an explicit null says "this daemon knows about the
bandwidth and could not read it". For a field whose whole point is that
nobody could see it before, that distinction is the feature.
tx_bandwidth_hz integer · int64 · nullable
sample_rate_sps integer · int64 · nullable
The part's sample rate. Also on /radio/rf-state; carried here so that
one call answers "how is the front end configured" instead of two that
have to be reconciled.
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/frequency" \
-H "Content-Type: application/json" \
-d '{
"center_frequency_hz": 0,
"channel": "string",
"tune_lo": true
}'import requests
BASE_URL = "http://192.168.2.1:8080"
response = requests.post(
f"{BASE_URL}/api/v1/radio/frequency",
json={
"center_frequency_hz": 0,
"channel": "string",
"tune_lo": True,
},
)
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/frequency"))
.json(&serde_json::json!({
"center_frequency_hz": 0,
"channel": "string",
"tune_lo": true
}))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{
"rx_state": "string",
"tx_state": "string",
"center_frequency_hz": 0,
"rx_gain_db": 0,
"tx_gain_db": 0,
"loopback": true,
"timing_lock": true,
"carrier_lock": true,
"rx_lo_hz": 0,
"rx_nco_offset_hz": 0,
"tx_nco_offset_hz": 0,
"profile_center_frequency_hz": 0,
"tx_lo_hz": 0,
"rx_rf_gain_db": 0.0,
"tx_rf_gain_db": 0.0,
"rx_bandwidth_hz": 0,
"tx_bandwidth_hz": 0,
"sample_rate_sps": 0
}/api/v1/radio/gainSet gain#
Request body application/jsonrequired
Responses
200OKapplication/json
rx_state requiredstring
tx_state requiredstring
center_frequency_hz requiredinteger · int64
rx_gain_db requiredinteger · int32
tx_gain_db requiredinteger · int32
loopbackrequiredboolean
timing_lock requiredboolean
carrier_lock requiredboolean
rx_lo_hz integer · int64 · nullable
rx_nco_offset_hz requiredinteger · int64
The DDC's digital offset from the RX LO, signed, in Hz.
tx_nco_offset_hz requiredinteger · int64
The DUC's digital offset from the TX LO, signed, in Hz.
profile_center_frequency_hz requiredinteger · int64
What the applied PROFILE declares as its centre, read from the inert
chan_cfg shadow. It describes the configuration, not the radio, and
the two diverge the moment anyone tunes by hand.
tx_lo_hz integer · int64 · nullable
rx_rf_gain_db number · double · nullable
The part's own gain, in dB at the connector. TX is an ATTENUATION.
tx_rf_gain_db number · double · nullable
rx_bandwidth_hz integer · int64 · nullable
The part's ANALOG filter width, per direction, read from IIO.
NOT a PL register — the AD9361 has none in this address space, which is
exactly why the datapath diagram could not show it. None means the
attribute could not be read, never a default: this is the value that sat
at 18 MHz for a 53 kHz signal (STE-998), and a plausible-looking number
here would be worse than an absence.
The part QUANTISES what is written, so this read-back legitimately differs from any requested width. It is the truth; the request is not.
SERIALISED EVEN WHEN NULL, unlike its neighbours above, and the difference
is deliberate. An ABSENT field cannot be told apart from an older daemon
that never had it; an explicit null says "this daemon knows about the
bandwidth and could not read it". For a field whose whole point is that
nobody could see it before, that distinction is the feature.
tx_bandwidth_hz integer · int64 · nullable
sample_rate_sps integer · int64 · nullable
The part's sample rate. Also on /radio/rf-state; carried here so that
one call answers "how is the front end configured" instead of two that
have to be reconciled.
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/gain" \
-H "Content-Type: application/json" \
-d '{
"rx_gain_db": 0,
"tx_gain_db": 0,
"domain": {}
}'import requests
BASE_URL = "http://192.168.2.1:8080"
response = requests.post(
f"{BASE_URL}/api/v1/radio/gain",
json={
"rx_gain_db": 0,
"tx_gain_db": 0,
"domain": {},
},
)
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/gain"))
.json(&serde_json::json!({
"rx_gain_db": 0,
"tx_gain_db": 0,
"domain": {}
}))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{
"rx_state": "string",
"tx_state": "string",
"center_frequency_hz": 0,
"rx_gain_db": 0,
"tx_gain_db": 0,
"loopback": true,
"timing_lock": true,
"carrier_lock": true,
"rx_lo_hz": 0,
"rx_nco_offset_hz": 0,
"tx_nco_offset_hz": 0,
"profile_center_frequency_hz": 0,
"tx_lo_hz": 0,
"rx_rf_gain_db": 0.0,
"tx_rf_gain_db": 0.0,
"rx_bandwidth_hz": 0,
"tx_bandwidth_hz": 0,
"sample_rate_sps": 0
}/api/v1/radio/loopbackEnable loopback#
Request body application/jsonrequired
enabledrequiredboolean
Responses
200OKapplication/json
rx_state requiredstring
tx_state requiredstring
center_frequency_hz requiredinteger · int64
rx_gain_db requiredinteger · int32
tx_gain_db requiredinteger · int32
loopbackrequiredboolean
timing_lock requiredboolean
carrier_lock requiredboolean
rx_lo_hz integer · int64 · nullable
rx_nco_offset_hz requiredinteger · int64
The DDC's digital offset from the RX LO, signed, in Hz.
tx_nco_offset_hz requiredinteger · int64
The DUC's digital offset from the TX LO, signed, in Hz.
profile_center_frequency_hz requiredinteger · int64
What the applied PROFILE declares as its centre, read from the inert
chan_cfg shadow. It describes the configuration, not the radio, and
the two diverge the moment anyone tunes by hand.
tx_lo_hz integer · int64 · nullable
rx_rf_gain_db number · double · nullable
The part's own gain, in dB at the connector. TX is an ATTENUATION.
tx_rf_gain_db number · double · nullable
rx_bandwidth_hz integer · int64 · nullable
The part's ANALOG filter width, per direction, read from IIO.
NOT a PL register — the AD9361 has none in this address space, which is
exactly why the datapath diagram could not show it. None means the
attribute could not be read, never a default: this is the value that sat
at 18 MHz for a 53 kHz signal (STE-998), and a plausible-looking number
here would be worse than an absence.
The part QUANTISES what is written, so this read-back legitimately differs from any requested width. It is the truth; the request is not.
SERIALISED EVEN WHEN NULL, unlike its neighbours above, and the difference
is deliberate. An ABSENT field cannot be told apart from an older daemon
that never had it; an explicit null says "this daemon knows about the
bandwidth and could not read it". For a field whose whole point is that
nobody could see it before, that distinction is the feature.
tx_bandwidth_hz integer · int64 · nullable
sample_rate_sps integer · int64 · nullable
The part's sample rate. Also on /radio/rf-state; carried here so that
one call answers "how is the front end configured" instead of two that
have to be reconciled.
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/loopback" \
-H "Content-Type: application/json" \
-d '{
"enabled": true
}'import requests
BASE_URL = "http://192.168.2.1:8080"
response = requests.post(
f"{BASE_URL}/api/v1/radio/loopback",
json={
"enabled": True,
},
)
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/loopback"))
.json(&serde_json::json!({
"enabled": true
}))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{
"rx_state": "string",
"tx_state": "string",
"center_frequency_hz": 0,
"rx_gain_db": 0,
"tx_gain_db": 0,
"loopback": true,
"timing_lock": true,
"carrier_lock": true,
"rx_lo_hz": 0,
"rx_nco_offset_hz": 0,
"tx_nco_offset_hz": 0,
"profile_center_frequency_hz": 0,
"tx_lo_hz": 0,
"rx_rf_gain_db": 0.0,
"tx_rf_gain_db": 0.0,
"rx_bandwidth_hz": 0,
"tx_bandwidth_hz": 0,
"sample_rate_sps": 0
}/api/v1/radio/resetReset#
Request body application/jsonrequired
scoperequiredstring
Responses
200OKapplication/json
rx_state requiredstring
tx_state requiredstring
center_frequency_hz requiredinteger · int64
rx_gain_db requiredinteger · int32
tx_gain_db requiredinteger · int32
loopbackrequiredboolean
timing_lock requiredboolean
carrier_lock requiredboolean
rx_lo_hz integer · int64 · nullable
rx_nco_offset_hz requiredinteger · int64
The DDC's digital offset from the RX LO, signed, in Hz.
tx_nco_offset_hz requiredinteger · int64
The DUC's digital offset from the TX LO, signed, in Hz.
profile_center_frequency_hz requiredinteger · int64
What the applied PROFILE declares as its centre, read from the inert
chan_cfg shadow. It describes the configuration, not the radio, and
the two diverge the moment anyone tunes by hand.
tx_lo_hz integer · int64 · nullable
rx_rf_gain_db number · double · nullable
The part's own gain, in dB at the connector. TX is an ATTENUATION.
tx_rf_gain_db number · double · nullable
rx_bandwidth_hz integer · int64 · nullable
The part's ANALOG filter width, per direction, read from IIO.
NOT a PL register — the AD9361 has none in this address space, which is
exactly why the datapath diagram could not show it. None means the
attribute could not be read, never a default: this is the value that sat
at 18 MHz for a 53 kHz signal (STE-998), and a plausible-looking number
here would be worse than an absence.
The part QUANTISES what is written, so this read-back legitimately differs from any requested width. It is the truth; the request is not.
SERIALISED EVEN WHEN NULL, unlike its neighbours above, and the difference
is deliberate. An ABSENT field cannot be told apart from an older daemon
that never had it; an explicit null says "this daemon knows about the
bandwidth and could not read it". For a field whose whole point is that
nobody could see it before, that distinction is the feature.
tx_bandwidth_hz integer · int64 · nullable
sample_rate_sps integer · int64 · nullable
The part's sample rate. Also on /radio/rf-state; carried here so that
one call answers "how is the front end configured" instead of two that
have to be reconciled.
400invalid scopeapplication/json
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/reset" \
-H "Content-Type: application/json" \
-d '{
"scope": "string"
}'import requests
BASE_URL = "http://192.168.2.1:8080"
response = requests.post(
f"{BASE_URL}/api/v1/radio/reset",
json={
"scope": "string",
},
)
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/reset"))
.json(&serde_json::json!({
"scope": "string"
}))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{
"rx_state": "string",
"tx_state": "string",
"center_frequency_hz": 0,
"rx_gain_db": 0,
"tx_gain_db": 0,
"loopback": true,
"timing_lock": true,
"carrier_lock": true,
"rx_lo_hz": 0,
"rx_nco_offset_hz": 0,
"tx_nco_offset_hz": 0,
"profile_center_frequency_hz": 0,
"tx_lo_hz": 0,
"rx_rf_gain_db": 0.0,
"tx_rf_gain_db": 0.0,
"rx_bandwidth_hz": 0,
"tx_bandwidth_hz": 0,
"sample_rate_sps": 0
}{}/api/v1/radio/rf/calibrateRF calibrate#
Back to the PL loopback: the AD9361 is bypassed entirely.
Responses
200OKapplication/json
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/rf/calibrate"import requests
BASE_URL = "http://192.168.2.1:8080"
response = requests.post(f"{BASE_URL}/api/v1/radio/rf/calibrate")
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/rf/calibrate"))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{}/api/v1/radio/rf/killRF kill#
EMERGENCY STOP: take RF off the connector, now.
WHAT ACTUALLY REMOVES RF, and what only looks like it. The TX attenuation
bottoms out at -89.75 dB and still emits a carrier. An empty DAC still leaks
the LO. Closing the modem's egress stops the samples without touching the
mixers — measured on this bench, that leaves the LO leakage on the analyser.
Only the transceiver's own state machine takes the transmitter down, so
ensm_mode = alert is the first thing this does and the one that decides
whether the button worked.
The digital steps follow rather than lead: they stop the chain feeding a part that is already off, which matters for what the board is doing when it comes back, not for whether it is emitting.
EVERY STEP IS READ BACK and reported by name. An emergency stop that reports success without checking is worse than no button: it converts "I am not transmitting" from something you verified into something you were told.
It is deliberately NOT a toggle. Coming back is the ordinary conditioning
sequence (satlinkctl chain up), which re-establishes the ENSM, the tuning
the part lost by leaving FDD, the profile and the path — in that order.
Responses
200OKapplication/json
ensm_mode requiredstring
What the part's ENSM reports AFTER the stop. alert is RF off.
stoppedrequiredarray of string
Steps that took effect, in the order they were applied.
failedrequiredarray of string
Steps that did NOT. Present even when RF is off, because a stop that half-worked and says nothing is the failure mode this exists against.
rf_off requiredboolean
Is RF actually off at the connector?
recoverrequiredstring
How to come back.
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/rf/kill"import requests
BASE_URL = "http://192.168.2.1:8080"
response = requests.post(f"{BASE_URL}/api/v1/radio/rf/kill")
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/rf/kill"))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{
"ensm_mode": "string",
"stopped": [
"string"
],
"failed": [
"string"
],
"rf_off": true,
"recover": "string"
}/api/v1/radio/rf/mode/airRF mode air#
ON AIR. Identical to mode/rf except the part's internal loopback stays
OPEN, so the signal reaches the mixers and the TX port.
The distinction is not cosmetic and it cost an evening: mode/rf closes the
AD9361's DIGITAL loopback, which returns TX to RX BEFORE the mixers. In that
mode the LOs, the gains and the TX attenuation have no effect whatsoever, so
nothing reaches an antenna and none of those settings explains why.
Responses
200OKapplication/json
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/rf/mode/air"import requests
BASE_URL = "http://192.168.2.1:8080"
response = requests.post(f"{BASE_URL}/api/v1/radio/rf/mode/air")
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/rf/mode/air"))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{}/api/v1/radio/rx{chan}/armRX arm#
Path parameters
chanrequiredstring
RX channel (1/2)
Responses
202Acceptedapplication/json
rx_state requiredstring
tx_state requiredstring
center_frequency_hz requiredinteger · int64
rx_gain_db requiredinteger · int32
tx_gain_db requiredinteger · int32
loopbackrequiredboolean
timing_lock requiredboolean
carrier_lock requiredboolean
rx_lo_hz integer · int64 · nullable
rx_nco_offset_hz requiredinteger · int64
The DDC's digital offset from the RX LO, signed, in Hz.
tx_nco_offset_hz requiredinteger · int64
The DUC's digital offset from the TX LO, signed, in Hz.
profile_center_frequency_hz requiredinteger · int64
What the applied PROFILE declares as its centre, read from the inert
chan_cfg shadow. It describes the configuration, not the radio, and
the two diverge the moment anyone tunes by hand.
tx_lo_hz integer · int64 · nullable
rx_rf_gain_db number · double · nullable
The part's own gain, in dB at the connector. TX is an ATTENUATION.
tx_rf_gain_db number · double · nullable
rx_bandwidth_hz integer · int64 · nullable
The part's ANALOG filter width, per direction, read from IIO.
NOT a PL register — the AD9361 has none in this address space, which is
exactly why the datapath diagram could not show it. None means the
attribute could not be read, never a default: this is the value that sat
at 18 MHz for a 53 kHz signal (STE-998), and a plausible-looking number
here would be worse than an absence.
The part QUANTISES what is written, so this read-back legitimately differs from any requested width. It is the truth; the request is not.
SERIALISED EVEN WHEN NULL, unlike its neighbours above, and the difference
is deliberate. An ABSENT field cannot be told apart from an older daemon
that never had it; an explicit null says "this daemon knows about the
bandwidth and could not read it". For a field whose whole point is that
nobody could see it before, that distinction is the feature.
tx_bandwidth_hz integer · int64 · nullable
sample_rate_sps integer · int64 · nullable
The part's sample rate. Also on /radio/rf-state; carried here so that
one call answers "how is the front end configured" instead of two that
have to be reconciled.
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/rx<chan>/arm"import requests
BASE_URL = "http://192.168.2.1:8080"
chan = "<chan>" # RX channel (1/2)
response = requests.post(f"{BASE_URL}/api/v1/radio/rx{chan}/arm")
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let chan = "<chan>"; // RX channel (1/2)
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/rx{chan}/arm"))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{
"rx_state": "string",
"tx_state": "string",
"center_frequency_hz": 0,
"rx_gain_db": 0,
"tx_gain_db": 0,
"loopback": true,
"timing_lock": true,
"carrier_lock": true,
"rx_lo_hz": 0,
"rx_nco_offset_hz": 0,
"tx_nco_offset_hz": 0,
"profile_center_frequency_hz": 0,
"tx_lo_hz": 0,
"rx_rf_gain_db": 0.0,
"tx_rf_gain_db": 0.0,
"rx_bandwidth_hz": 0,
"tx_bandwidth_hz": 0,
"sample_rate_sps": 0
}/api/v1/radio/rx{chan}/disarmRX disarm#
Path parameters
chanrequiredstring
channel number
Responses
200OKapplication/json
rx_state requiredstring
tx_state requiredstring
center_frequency_hz requiredinteger · int64
rx_gain_db requiredinteger · int32
tx_gain_db requiredinteger · int32
loopbackrequiredboolean
timing_lock requiredboolean
carrier_lock requiredboolean
rx_lo_hz integer · int64 · nullable
rx_nco_offset_hz requiredinteger · int64
The DDC's digital offset from the RX LO, signed, in Hz.
tx_nco_offset_hz requiredinteger · int64
The DUC's digital offset from the TX LO, signed, in Hz.
profile_center_frequency_hz requiredinteger · int64
What the applied PROFILE declares as its centre, read from the inert
chan_cfg shadow. It describes the configuration, not the radio, and
the two diverge the moment anyone tunes by hand.
tx_lo_hz integer · int64 · nullable
rx_rf_gain_db number · double · nullable
The part's own gain, in dB at the connector. TX is an ATTENUATION.
tx_rf_gain_db number · double · nullable
rx_bandwidth_hz integer · int64 · nullable
The part's ANALOG filter width, per direction, read from IIO.
NOT a PL register — the AD9361 has none in this address space, which is
exactly why the datapath diagram could not show it. None means the
attribute could not be read, never a default: this is the value that sat
at 18 MHz for a 53 kHz signal (STE-998), and a plausible-looking number
here would be worse than an absence.
The part QUANTISES what is written, so this read-back legitimately differs from any requested width. It is the truth; the request is not.
SERIALISED EVEN WHEN NULL, unlike its neighbours above, and the difference
is deliberate. An ABSENT field cannot be told apart from an older daemon
that never had it; an explicit null says "this daemon knows about the
bandwidth and could not read it". For a field whose whole point is that
nobody could see it before, that distinction is the feature.
tx_bandwidth_hz integer · int64 · nullable
sample_rate_sps integer · int64 · nullable
The part's sample rate. Also on /radio/rf-state; carried here so that
one call answers "how is the front end configured" instead of two that
have to be reconciled.
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/rx<chan>/disarm"import requests
BASE_URL = "http://192.168.2.1:8080"
chan = "<chan>" # channel number
response = requests.post(f"{BASE_URL}/api/v1/radio/rx{chan}/disarm")
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let chan = "<chan>"; // channel number
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/rx{chan}/disarm"))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{
"rx_state": "string",
"tx_state": "string",
"center_frequency_hz": 0,
"rx_gain_db": 0,
"tx_gain_db": 0,
"loopback": true,
"timing_lock": true,
"carrier_lock": true,
"rx_lo_hz": 0,
"rx_nco_offset_hz": 0,
"tx_nco_offset_hz": 0,
"profile_center_frequency_hz": 0,
"tx_lo_hz": 0,
"rx_rf_gain_db": 0.0,
"tx_rf_gain_db": 0.0,
"rx_bandwidth_hz": 0,
"tx_bandwidth_hz": 0,
"sample_rate_sps": 0
}/api/v1/radio/statusStatus#
Responses
200OKapplication/json
rx_state requiredstring
tx_state requiredstring
center_frequency_hz requiredinteger · int64
rx_gain_db requiredinteger · int32
tx_gain_db requiredinteger · int32
loopbackrequiredboolean
timing_lock requiredboolean
carrier_lock requiredboolean
rx_lo_hz integer · int64 · nullable
rx_nco_offset_hz requiredinteger · int64
The DDC's digital offset from the RX LO, signed, in Hz.
tx_nco_offset_hz requiredinteger · int64
The DUC's digital offset from the TX LO, signed, in Hz.
profile_center_frequency_hz requiredinteger · int64
What the applied PROFILE declares as its centre, read from the inert
chan_cfg shadow. It describes the configuration, not the radio, and
the two diverge the moment anyone tunes by hand.
tx_lo_hz integer · int64 · nullable
rx_rf_gain_db number · double · nullable
The part's own gain, in dB at the connector. TX is an ATTENUATION.
tx_rf_gain_db number · double · nullable
rx_bandwidth_hz integer · int64 · nullable
The part's ANALOG filter width, per direction, read from IIO.
NOT a PL register — the AD9361 has none in this address space, which is
exactly why the datapath diagram could not show it. None means the
attribute could not be read, never a default: this is the value that sat
at 18 MHz for a 53 kHz signal (STE-998), and a plausible-looking number
here would be worse than an absence.
The part QUANTISES what is written, so this read-back legitimately differs from any requested width. It is the truth; the request is not.
SERIALISED EVEN WHEN NULL, unlike its neighbours above, and the difference
is deliberate. An ABSENT field cannot be told apart from an older daemon
that never had it; an explicit null says "this daemon knows about the
bandwidth and could not read it". For a field whose whole point is that
nobody could see it before, that distinction is the feature.
tx_bandwidth_hz integer · int64 · nullable
sample_rate_sps integer · int64 · nullable
The part's sample rate. Also on /radio/rf-state; carried here so that
one call answers "how is the front end configured" instead of two that
have to be reconciled.
Request
curl "http://192.168.2.1:8080/api/v1/radio/status"import requests
BASE_URL = "http://192.168.2.1:8080"
response = requests.get(f"{BASE_URL}/api/v1/radio/status")
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let response = reqwest::Client::new()
.get(format!("{base_url}/api/v1/radio/status"))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{
"rx_state": "string",
"tx_state": "string",
"center_frequency_hz": 0,
"rx_gain_db": 0,
"tx_gain_db": 0,
"loopback": true,
"timing_lock": true,
"carrier_lock": true,
"rx_lo_hz": 0,
"rx_nco_offset_hz": 0,
"tx_nco_offset_hz": 0,
"profile_center_frequency_hz": 0,
"tx_lo_hz": 0,
"rx_rf_gain_db": 0.0,
"tx_rf_gain_db": 0.0,
"rx_bandwidth_hz": 0,
"tx_bandwidth_hz": 0,
"sample_rate_sps": 0
}/api/v1/radio/tx{chan}/startTX start#
Path parameters
chanrequiredstring
channel number
Responses
202Acceptedapplication/json
rx_state requiredstring
tx_state requiredstring
center_frequency_hz requiredinteger · int64
rx_gain_db requiredinteger · int32
tx_gain_db requiredinteger · int32
loopbackrequiredboolean
timing_lock requiredboolean
carrier_lock requiredboolean
rx_lo_hz integer · int64 · nullable
rx_nco_offset_hz requiredinteger · int64
The DDC's digital offset from the RX LO, signed, in Hz.
tx_nco_offset_hz requiredinteger · int64
The DUC's digital offset from the TX LO, signed, in Hz.
profile_center_frequency_hz requiredinteger · int64
What the applied PROFILE declares as its centre, read from the inert
chan_cfg shadow. It describes the configuration, not the radio, and
the two diverge the moment anyone tunes by hand.
tx_lo_hz integer · int64 · nullable
rx_rf_gain_db number · double · nullable
The part's own gain, in dB at the connector. TX is an ATTENUATION.
tx_rf_gain_db number · double · nullable
rx_bandwidth_hz integer · int64 · nullable
The part's ANALOG filter width, per direction, read from IIO.
NOT a PL register — the AD9361 has none in this address space, which is
exactly why the datapath diagram could not show it. None means the
attribute could not be read, never a default: this is the value that sat
at 18 MHz for a 53 kHz signal (STE-998), and a plausible-looking number
here would be worse than an absence.
The part QUANTISES what is written, so this read-back legitimately differs from any requested width. It is the truth; the request is not.
SERIALISED EVEN WHEN NULL, unlike its neighbours above, and the difference
is deliberate. An ABSENT field cannot be told apart from an older daemon
that never had it; an explicit null says "this daemon knows about the
bandwidth and could not read it". For a field whose whole point is that
nobody could see it before, that distinction is the feature.
tx_bandwidth_hz integer · int64 · nullable
sample_rate_sps integer · int64 · nullable
The part's sample rate. Also on /radio/rf-state; carried here so that
one call answers "how is the front end configured" instead of two that
have to be reconciled.
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/tx<chan>/start"import requests
BASE_URL = "http://192.168.2.1:8080"
chan = "<chan>" # channel number
response = requests.post(f"{BASE_URL}/api/v1/radio/tx{chan}/start")
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let chan = "<chan>"; // channel number
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/tx{chan}/start"))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{
"rx_state": "string",
"tx_state": "string",
"center_frequency_hz": 0,
"rx_gain_db": 0,
"tx_gain_db": 0,
"loopback": true,
"timing_lock": true,
"carrier_lock": true,
"rx_lo_hz": 0,
"rx_nco_offset_hz": 0,
"tx_nco_offset_hz": 0,
"profile_center_frequency_hz": 0,
"tx_lo_hz": 0,
"rx_rf_gain_db": 0.0,
"tx_rf_gain_db": 0.0,
"rx_bandwidth_hz": 0,
"tx_bandwidth_hz": 0,
"sample_rate_sps": 0
}/api/v1/radio/tx{chan}/stopTX stop#
Path parameters
chanrequiredstring
channel number
Responses
200OKapplication/json
rx_state requiredstring
tx_state requiredstring
center_frequency_hz requiredinteger · int64
rx_gain_db requiredinteger · int32
tx_gain_db requiredinteger · int32
loopbackrequiredboolean
timing_lock requiredboolean
carrier_lock requiredboolean
rx_lo_hz integer · int64 · nullable
rx_nco_offset_hz requiredinteger · int64
The DDC's digital offset from the RX LO, signed, in Hz.
tx_nco_offset_hz requiredinteger · int64
The DUC's digital offset from the TX LO, signed, in Hz.
profile_center_frequency_hz requiredinteger · int64
What the applied PROFILE declares as its centre, read from the inert
chan_cfg shadow. It describes the configuration, not the radio, and
the two diverge the moment anyone tunes by hand.
tx_lo_hz integer · int64 · nullable
rx_rf_gain_db number · double · nullable
The part's own gain, in dB at the connector. TX is an ATTENUATION.
tx_rf_gain_db number · double · nullable
rx_bandwidth_hz integer · int64 · nullable
The part's ANALOG filter width, per direction, read from IIO.
NOT a PL register — the AD9361 has none in this address space, which is
exactly why the datapath diagram could not show it. None means the
attribute could not be read, never a default: this is the value that sat
at 18 MHz for a 53 kHz signal (STE-998), and a plausible-looking number
here would be worse than an absence.
The part QUANTISES what is written, so this read-back legitimately differs from any requested width. It is the truth; the request is not.
SERIALISED EVEN WHEN NULL, unlike its neighbours above, and the difference
is deliberate. An ABSENT field cannot be told apart from an older daemon
that never had it; an explicit null says "this daemon knows about the
bandwidth and could not read it". For a field whose whole point is that
nobody could see it before, that distinction is the feature.
tx_bandwidth_hz integer · int64 · nullable
sample_rate_sps integer · int64 · nullable
The part's sample rate. Also on /radio/rf-state; carried here so that
one call answers "how is the front end configured" instead of two that
have to be reconciled.
Request
curl -X POST "http://192.168.2.1:8080/api/v1/radio/tx<chan>/stop"import requests
BASE_URL = "http://192.168.2.1:8080"
chan = "<chan>" # channel number
response = requests.post(f"{BASE_URL}/api/v1/radio/tx{chan}/stop")
response.raise_for_status()
print(response.json())let base_url = "http://192.168.2.1:8080";
let chan = "<chan>"; // channel number
let response = reqwest::Client::new()
.post(format!("{base_url}/api/v1/radio/tx{chan}/stop"))
.send()
.await?
.error_for_status()?;
let body: serde_json::Value = response.json().await?;
println!("{body:#}");Response
{
"rx_state": "string",
"tx_state": "string",
"center_frequency_hz": 0,
"rx_gain_db": 0,
"tx_gain_db": 0,
"loopback": true,
"timing_lock": true,
"carrier_lock": true,
"rx_lo_hz": 0,
"rx_nco_offset_hz": 0,
"tx_nco_offset_hz": 0,
"profile_center_frequency_hz": 0,
"tx_lo_hz": 0,
"rx_rf_gain_db": 0.0,
"tx_rf_gain_db": 0.0,
"rx_bandwidth_hz": 0,
"tx_bandwidth_hz": 0,
"sample_rate_sps": 0
}