27  Baseband

org.arl.unet.Services.BASEBAND

27.1 Overview

The BASEBAND service exposes the modem as a software-defined modem, giving direct access to the raw acoustic waveforms beneath the framing and modulation of the physical layer (Chapter 17). This makes it possible to develop and test custom acoustic communication and signal processing techniques without building new hardware.

The service supports three basic operations:

  • Transmitting an arbitrary signal, using a TxBasebandSignalReq. The signal may be specified at passband (real samples) or baseband (complex samples around a carrier frequency fc), and an optional preamble may be prepended for detection at the receiver.
  • Recording a signal from the channel, using a RecordBasebandSignalReq. The recording can be scheduled to start at a precise time, and is returned as an RxBasebandSignalNtf.
  • Detecting signals passively. When a configured detector (for example, a matched filter for a known preamble) fires, a DetectionNtf is generated, optionally triggering a recording.

Received signals are delivered as RxBasebandSignalNtf messages, and transmissions are bracketed by TxStartNtf and TxEndNtf notifications for precise timing. The default baseband agent is accessed as bb (on most modems, the same agent as phy).

Warning

Baseband transmissions bypass medium access control entirely — the modem transmits your signal without consulting the MAC agent, and other nodes cannot even decode it to know how long the channel is busy. On a shared channel, reserve the channel through the MAC service (Chapter 19) before transmitting signals of any significant duration.

27.2 Capabilities

Optional features of a baseband agent are advertised through capabilities (org.arl.unet.bb.BasebandCapability):

  • TIMED_BBTX – transmission of signals at a specified time (txTime)
  • TIMED_BBREC – recording of signals starting at a specified time (recStartTime)
  • BB_STREAMING – continuous streaming of received baseband signals
  • PB_STREAMING – continuous streaming of received passband signals

27.3 Messages

Agents providing this service honor the following requests:

  • TxBasebandSignalReq – transmit a baseband signal

    Field Type Default Remarks
    channels int 1
    fc float -1.0 carrier frequency in Hz (0 for passband)
    fs float -1.0 sampling rate in Hz
    powerLevel float
    preamble int 0 preamble ID to prepend (0 = none)
    signal float[] baseband or passband signal samples
    signal__isComplex boolean false
    txStartTime long
    wakeup boolean false
  • RecordBasebandSignalReq – record a baseband signal

    Field Type Default Remarks
    recLength int number of samples to record
    recStartTime long recording start time (requires TIMED_BBREC)
  • GetPreambleSignalReq – get the baseband signal for a preamble

    Field Type Default Remarks
    preamble int -1
  • ClearReq – clear all queued (and ongoing, when possible) transmissions

A transmission request is answered with an AGREE once the signal is accepted (a REFUSE if the signal is too long, the preamble ID is invalid, or a requested option is unsupported), and the transmission is then bracketed by notifications marking its exact start and end. A recording request is answered with an AGREE, followed by the recorded signal. Notifications generated by this service:

  • RxBasebandSignalNtf – received/recorded baseband signal

    Field Type Default Remarks
    channels int 1
    fc float -1.0
    fs float -1.0
    location double[]
    preamble int 0
    preambleLength int 0
    rssi float NaN
    rxStartTime long
    signal float[]
    signal__isComplex boolean false
  • TxStartNtf – baseband transmission started

    Field Type Default Remarks
    preamble int -1
    txDuration int
    txStartTime long
  • TxEndNtf – baseband transmission completed

    Field Type Default Remarks
    location double[]
    preamble int -1
    txStartTime long
  • DetectionNtf – signal detected by a detector

    Field Type Default Remarks
    captureStartTime long
    detector float 0.0
    preamble int -1
    rxDuration int
    rxStartTime long

The recorded RxBasebandSignalNtf is sent to the agent that requested the recording; recordings triggered by detections, along with TxStartNtf, TxEndNtf and DetectionNtf, are published on the agent’s topic. The signal of an RxBasebandSignalNtf is a flat array of interleaved real/imaginary baseband samples (or real samples for passband), along with its center frequency fc and sampling rate fs. In the Python and Julia APIs, the same signals appear as native complex arrays. (Why modems favor the complex baseband representation is explained in a callout in Chapter 2.)

27.4 Parameters

  • analogInterface :: String – analog interface name

  • dacrate :: float – DAC sampling rate (Hz)

  • adcrate :: float – ADC sampling rate (Hz)

  • adcChannels :: int – number of receiver channels

  • dacChannels :: int – number of transmitter channels

  • enable :: boolean – enable baseband data acquisition

  • carrierFrequency :: float – carrier frequency (Hz)

  • basebandRate :: float – baseband sampling rate (Hz)

    The baseband sampling rate is the rate at which the baseband signal is sampled.

  • maxPreambleID :: int – maximum preamble ID

  • maxInAirPowerLevel :: float – transmit power if modem is near surface or in air

    This parameter sets the transmit power level limit used when the modem is near the surface or in air for safety. If depth information (node.depth) is unavailable, this parameter is not used.

    Example:

    bb.maxInAirPowerLevel = -20     // Set maximum in-air transmit power to -20 dB
    
  • depthToConsiderInWater :: float – min depth to consider modem in water (m)

  • maxSignalLength :: int – baseband signal length (samples)

  • powerLevel :: float[] – signal transmission power level (dB)

    If set as a scalar, the value is applied to all channels. If set as an array, the value is applied to each channel individually.

  • maxPowerLevel :: float – maximum supported power level (dB)

  • minPowerLevel :: float – minimum supported power level (dB)

  • refPowerLevel :: float (nullable) – reference power level (dB)

    Reference source level in dB re 1 uPa @ 1m when the transmit power level is set to 0 dB.

  • gain :: float[] – preamplifier gain (dB)

  • rxSensitivity :: float[] (nullable) – receiver sensitivity (dB)

    Receiver sensitivity in dB re 1/uPa. May be null if unknown.

  • record :: boolean – enable recording of baseband data to persistent storage

  • time :: long – baseband timestamp (µs)

  • syncBasebandTime :: long – baseband clock at last sync event

  • syncSystemTime :: long – system time at last sync event (µs)

  • clockCalib :: float – clock calibration ratio (1.0 = no correction)

  • busy :: boolean – true if signal transmission ongoing, false otherwise

  • fullduplex :: boolean – allow reception when transmitting

    Full duplex mode. In this mode, the baseband service is able to transmit and receive simultaneously. When disabled, receptions are inhibited while a transmission is ongoing.

  • mute :: boolean – mute output signal

    Mute the output data stream. Output data streams are automatically muted after transmission. A user may unmute early to avoid delays for time sensitive transmissions.

  • standby :: float – idle time before putting power amplifier in standby (seconds)

    Power amplifier is shutdown automatically after the specified idle time after the last transmission.

    A value of 0 shutsdown the power amplifier immediately after a transmission, only if it was turned on automatically for that transmission.

    A value of -1 disables auto-shutdown.

    Examples:

    bb.standby = 15      // shutdown poweramp after 15 s idle time
    bb.standby = -1      // disable auto-shutdown of poweramp
    
  • pbsblk :: int – passband streaming block size (samples)

  • pbscnt :: int – number of passband data blocks to stream

    Setting this parameter starts streaming of passband data for a specified number of blocks. A value of 0 stops streaming. A value of -1 enables long-term streaming. Setting this parameter to -1 may affect modem performance. For applications requiring continuous streaming, consider using UDP based streaming using bb.pbsport.

    Example:

    bb.pbscnt = 10       // stream 10 blocks of pasband data
    
  • bbsblk :: int – baseband streaming block size (samples)

  • bbscnt :: int – number of baseband data blocks to stream

    Setting this parameter starts streaming of baseband data for a specified number of blocks. A value of 0 stops streaming. A value of -1 enables long-term streaming. Setting this parameter to -1 may affect modem performance. For applications requiring continuous streaming, consider using UDP based streaming using bb.bbsport.

    Example:

    bb.bbscnt = 10       // stream 10 blocks of baseband data
    
  • ip :: String – IP address for UDP streaming

    Setting this parameter to a valid IPv4 address enables UDP streaming of baseband or passband data. A value of “0.0.0.0” or “none” disables streaming. This parameter must be set along with either bb.pbsport or bb.bbsport to enable streaming.

    Examples:

    bb.ip = '192.168.0.100' // set destination IP address
    bb.ip = '0.0.0.0'       // disable streaming
    
  • pbsport :: int – passband streaming UDP port

    Setting this parameter (along with bb.ip) starts streaming of passband data over UDP. A value of 0 stops streaming. Passband data is streamed as a 12-byte header followed by float32 samples with interleaved channels.

    Header format:

    byte 0: uint8 version number (0x01)
    byte 1: uint8 channel count
    bytes 2-3: uint16 sequence number
    bytes 4-7: float32 carrier frequency (Hz), 0 for passband
    bytes 8-11: float32 sampling rate (Sa/s)
    

    All float32 and uint16 are in little-endian byte order.

    Example:

    bb.ip = '192.168.0.100' // set destination IP address
    bb.pbsport = 8080       // start streaming to UDP port 8080
    bb.pbsport = 0          // stop streaming
    
  • bbsport :: int – baseband streaming UDP port

    Setting this parameter (along with bb.ip) starts streaming of baseband data over UDP. A value of 0 stops streaming. Baseband data is streamed as a 12-byte header followed by complex float32 samples (real, imaginary) with interleaved channels.

    Header format:

    byte 0: uint8 version number (0x01)
    byte 1: uint8 channel count
    bytes 2-3: uint16 sequence number
    bytes 4-7: float32 carrier frequency (Hz)
    bytes 8-11: float32 sampling rate (Sa/s)
    

    All float32 and uint16 are in little-endian byte order.

    Example:

    bb.ip = '192.168.0.100' // set destination IP address
    bb.bbsport = 8080       // start streaming to UDP port 8080
    bb.bbsport = 0          // stop streaming
    
  • noise :: float[] – ambient noise floor estimate (dB re full scale)

  • wakeupDelay :: int – gap between wakeup signal and transmission (ms)

  • wakeupSignal :: float[] – baseband samples of wakeup signal

The noise parameter reports one estimate per receiver channel, using the same dB-re-full-scale reference as the rssi reported in received-signal notifications — so the two can be compared directly to judge received signal levels against the background noise floor.

27.5 Detector parameters

Each preamble detector exposes indexed parameters, accessed as bb[1], bb[2], etc.:

  • preamble :: float[] – baseband samples for detection preamble

    Preambles are specified as complex baseband samples, with alternate samples in the array being real and imaginary parts. When setting a preamble, one may specify an array or use supported preset preambles.

    Example:

    bb[3].preamble = hfm                      // default HFM
    bb[3].preamble = hfm(0.04)                // HFM 40 ms duration
    bb[3].preamble = lfm(18000, 30000, 0.02)  // LFM 18-30 kHz 20 ms duration
    bb[3].preamble = lfmdn                    // LFM default downchirp
    bb[3].preamble = mseq                     // default m-sequence
    bb[3].preamble = mseq(0.04)               // m-sequence 40 ms duration
    bb[3].preamble = janus                    // default JANUS
    bb[3].preamble = janus(20000, 320)        // JANUS at 20 kHz, 320 Hz step
    bb[3].preamble = swig1                    // default SWiG1
    bb[3].preamble = swig1(20000, 320)        // SWiG1 at 20 kHz, 320 Hz step
    
  • threshold :: float – detection threshold (0-1 range, 0 to disable)

  • inhibit :: int – detection inhibition (baseband sample count)

    Detections are reported only if there are no stronger detections within the specified number of baseband samples before or after them. The capture starts at the time of the strongest detection in the overall inhibition period. This period may be longer than inhibit if there is a sequence of detections of increasing strength.

  • capture :: int – number of baseband samples to capture at each detection

  • coherence :: int – baseband samples summed coherently (0 = all)

  • stride :: int – stride length for detector metric computation

  • metricStatistics :: float – detection metric statistics

27.6 Commands

  • bbrec – make a baseband recording

    Examples:

    bbrec 1000                 // record 1000 baseband samples
    bbrec 1000, 87897287       // schedule a recording at given PHY time
    
  • bbtx – transmit a baseband signal

    Examples:

    bbtx sig                 // transmit complex baseband signal
    bbtx sig, 87897287       // schedule a transmission at given PHY time
    
  • pbtx – transmit a passband signal sampled at 8 x basebandRate

    Examples:

    pbtx sig                 // transmit real passband signal
    pbtx sig, 87897287       // schedule a transmission at given PHY time
    
  • cw – create a tonal signal with unit amplitude

    Examples:

    cw(10000, 0.5)           // 0.5 second 10 kHz baseband tonal
    cw(15000, 0.2, 0)        // 0.2 second 15 kHz passband tonal
    
  • sweep – create a linear sweep with unit amplitude

    Examples:

    sweep(10000, 12000, 0.5)     // 0.5 second baseband sweep from 10-12 kHz
    sweep(10000, 12000, 0.5, 0)  // 0.5 second passband sweep from 10-12 kHz
    
  • wav – load a wav file (mono track, sampling rate should be same as bb.dacrate)

    Examples:

    wav 'test.wav'              // loads a wav file
    
  • save – save signal to a file in the scripts folder

    Example:

    save 'data.txt', ntf           // save notification signal to file
    save 'data.txt', ntf.signal, 2 // save signal in 2-column format
    
  • load – load floating point signal from a file in the scripts folder

    Example:

    signal = load('data.txt')      // load data from file

27.7 Examples

Transmit a continuous-wave (CW) tone with bbtx. The cw(freq, duration) command builds a baseband tonal signal — here a 0.5 s tone at 24 kHz, the simulated modem’s carrier frequency (the tone must lie within the modem’s baseband passband):

> bbtx cw(24000, 0.5)
AGREE
phy >> TxStartNtf:INFORM[txStartTime:3257907923 txDuration:500000]
phy >> TxEndNtf:INFORM[txStartTime:3257907923]

The same tone can be generated at passband — real samples at the modem’s DAC rate — by passing 0 as a third argument (the carrier frequency) to cw(). Passband signals are transmitted with pbtx. The simulated modem has no DAC (there is no actual audio path), so this one needs a real modem:

> pbtx cw(24000, 0.25, 0)
AGREE

Generate both versions of a signal and compare their lengths — the passband representation needs many times more samples to carry the same information, which is why baseband is the default.

Then capture samples from the channel with bbrec, which records a given number of baseband samples and returns them as an RxBasebandSignalNtf:

> bbrec 1000
AGREE
phy >> RxBasebandSignalNtf:INFORM[rxStartTime:3260254923 fc:24000.0 fs:24000.0 (1000 baseband samples)]

The recorded notification carries the captured signal, along with its center frequency fc and sampling rate fs. You can save it for offline analysis:

> ntf = bbrec(1000)
AGREE
phy >> RxBasebandSignalNtf:INFORM[rxStartTime:3260959923 fc:24000.0 fs:24000.0 (1000 baseband samples)]
> save 'rec.txt', ntf.signal, 2
OK

Recordings can also be scheduled. On an agent with the TIMED_BBREC capability, a RecordBasebandSignalReq carrying a recStartTime starts recording at exactly that instant on the physical-layer clock — here, 2 s in the future:

> phy << new RecordBasebandSignalReq(recLength: 1000, recStartTime: phy.time + 2000000)
AGREE
phy >> RxBasebandSignalNtf:INFORM[rxStartTime:3267665923 fc:24000.0 fs:24000.0 (1000 baseband samples)]
Note

In simulation, bbrec returns placeholder samples of the requested length, so the captured signal is not a meaningful recording of the transmitted tone. Run the same commands against a real modem to capture actual channel data.

On a real modem, the recording machinery has one more trick: it can record the past. The modem continuously retains the last few seconds of received baseband samples in a buffer, so a recording that starts slightly in the past is served from that buffer. A negative recStartTime is interpreted relative to the current time, making “give me the last 2 seconds” a one-liner. Asking for a start time further back than the buffer reaches is refused (Start time too far in the past). The simulator does not model this buffer, so this is one to try against real hardware:

> phy << new RecordBasebandSignalReq(recLength: 12000, recStartTime: -2000000)
AGREE
phy >> RxBasebandSignalNtf:INFORM[rxTime:42101332 rssi:-38.2 fc:12000 fs:12000 (12000 baseband samples)]

This is remarkably useful in practice: when a detector fires or a frame arrives, the signal that caused it has already happened — recording the past lets you go back and capture it.

27.8 Preambles & detection

Every frame transmission begins with a preamble — a known signal that receivers continuously correlate against, so that they can detect an incoming frame in the noise and know when it starts. The baseband service exposes this machinery directly: preambles can be transmitted on their own, and detectors can be configured to report whenever a chosen preamble is heard.

Each detector is configured through the indexed parameters bb[1], bb[2], … (see Detector parameters). Detectors 1 and 2 correlate against the preambles of the CONTROL and DATA channels respectively. A detector is enabled by setting its threshold to a value between 0 and 1 (0 disables it): a low threshold detects weaker signals but false-alarms more often in noise, a high threshold does the reverse (Chapter 17).

Let’s watch a detection happen. On node B, we subscribe to the physical agent’s topic (where DetectionNtfs are published); on node A, we transmit a bare CONTROL preamble — a TxBasebandSignalReq with a preamble but no signal:

> phy << new TxBasebandSignalReq(preamble: 1)
AGREE
phy >> TxStartNtf:INFORM[txStartTime:2667285038 txDuration:20000]
phy >> TxEndNtf:INFORM[txStartTime:2667285038]

Node B detects the preamble and reports it:

phy >> DetectionNtf:INFORM[preamble:1 rxStartTime:2378535222 rxDuration:582000]
phy >> RxFrameStartNtf:INFORM[type:CONTROL rxStartTime:2378535222 rxDuration:582000]
phy >> RxBasebandSignalNtf:INFORM[rxStartTime:2378535222 preamble:1 fc:24000.0 fs:24000.0 (13968 baseband samples)]
phy >> BadFrameNtf:INFORM[type:CONTROL rxStartTime:2378535222 rssi:-72.6]

Since preamble 1 announces a CONTROL frame, the receiver also treats the detection as the start of an incoming frame (an RxFrameStartNtf, Chapter 17); as no frame data follows a bare preamble, no RxFrameNtf ever arrives — on a real modem, the attempted decode eventually fails with a BadFrameNtf. This makes a bare preamble a cheap way to exercise a receiver’s detection chain.

A few more things the detector machinery can do:

  • Detection-triggered capture. Setting bb[n].capture to a sample count makes each detection trigger a recording of that many baseband samples, published as an RxBasebandSignalNtf on the agent’s topic — detect-and-capture without any round-trip.
  • Custom preambles. The bb[n].preamble parameter accepts arbitrary baseband samples, or preset generators such as hfm, lfm(f1, f2, duration) and mseq — so you can define your own signaling alongside the standard channels.
  • Channel impulse-response estimation. The exact baseband samples of any preamble are available via a GetPreambleSignalReq (or bb[n].preamble). Record the channel while a peer transmits that preamble, cross-correlate the recording with the known preamble signal, and the peaks give you an estimate of the channel’s impulse response — multipath structure included.

27.9 Implementations

The BASEBAND service is not loaded by setup.groovy; it is provided by the modem, alongside the PHYSICAL (Chapter 17) and, where supported, DOA (Chapter 26) services (see Chapter 13). Either way, the higher layers see the same BASEBAND service and messages described above.

27.9.1 Modem firmware (bb)

Class Services Capabilities Availability
modem firmware BASEBAND modem-dependent commercial modems

On a commercial modem, the baseband service is backed by the modem firmware running on the modem’s signal-processing hardware. From the rest of the stack’s point of view, there is simply a bb agent provides BASEBAND, exposing the modem’s software-defined modem capabilities — arbitrary signal transmission, timed recording, preamble detectors, and (on supported hardware) signal streaming.

27.9.2 HalfDuplexModem (phy)

In simulation, the HalfDuplexModem agent (Chapter 17) provides the baseband interface with the TIMED_BBTX and TIMED_BBREC capabilities. Transmissions and detections are modelled with correct timing and energy, but recordings return placeholder samples of the correct length rather than a true capture of the channel — the simulator does not synthesize received waveforms. Higher-layer agents that depend on baseband access therefore run unchanged, but signal content is only meaningful against real hardware.