16. Baseband service
Software-defined modems often allow developers to transmit and record arbitrary waveforms. This functionality is encapsulated in the baseband service of UnetStack.
|Agents implementing the baseband service typically directly access the channel, bypassing any MAC protocol that may be in use in the network. It is highly recommended that clients wishing to use the baseband service for transmitting arbitrary waveforms consult with the MAC service for advice on when it is safe to access the channel, so as not to adversely affect the network performance.|
Agents offering the baseband service are most commonly modem drivers (and simulators). They support a set of messages and parameters that are explained below. Baseband service providers may also provide optional capabilities to send or record signals at a specified time, or based on premable detection.
Agents supporting the baseband service provide messages for arbitrary signal transmission and recording:
During signal transmission, an agent implementing the baseband service sends out
, as described in the PHYSICAL service (
). Similarly, if recording is triggered on a preamble detection, the agent sends out a
as described in the PHYSICAL service.
Detection preambles are often added to transmitted signals for the receiving modem to identify the incoming signals. The receiving modem can capture the signal when it detects the preamble, and then send a
. If a modem supports multiple preambles, the
can specify the
to be used. Setting the
to 0 results in a signal transmission without a preamble. Such a transmission will ordinarily not be received by any modem, unless it is recorded by an explicit
at the appropriate time.
Agents offering the baseband service support the following parameter:
carrierFrequency— default carrier frequency for baseband signals (Hz)
basebandRate— default baseband sampling rate for baseband signals (samples/s)
maxPreambleID— maximum preamble identifier supported
maxSignalLength— maximum baseband signal length (in samples) for transmission/reception
signalPowerLevel— signal transmission power level in dB re
refPowerLevelis specified in the PHYSICAL service)
Agents may support several optional capabilities:
Agents advertising this capability are able to transmit signals at specified time (on a best effort basis). The time is given in the
attribute of the
Agents advertising this capability are able to record signals at specified time (on a best effort basis). The time is given in the
attribute of the
16.2. Baseband and passband signals
Communication systems usually represent signals in a complex baseband representation, as this allows them to be sampled at a lower sampling rate than real passband signals. Passband signals have to be sampled at more than twice the highest frequency (Nyquist criterion). Baseband signals need to be sampled at more than twice the bandwidth. Since the bandwidth is typically much lesser than the carrier frequency, the baseband representation is usually more economical than the passband representation.
While the baseband service is aimed at signals represented in the complex baseband representation, it also supports signals represented as real passband samples. Such signals are identified by setting the
(carrier frequency) field of the
to 0. Modems offering the baseband service may optionally support passband signal transmission and recording.
Since Java and Groovy do not support complex numbers natively, the complex baseband signals are represented by an array of floats with alternate samples from the in-phase (real part) and quadrature (imaginary part) channels. In languages that support complex numbers (e.g. Python and Julia), the signals are represented as arrays (or lists) of complex numbers.
If all this seems to be confusing, don’t worry, it’ll become clear as we go through examples shortly.
16.3. Transmitting and recording arbitrary signals
, you already saw how to transmit and record arbitrary signals. We then used the convenience functions
for simplicity. In this chapter, we will do the same thing by sending the
messages instead. As you’ll see, these messages provide you greater control, and can also be sent via the UnetSocket API or a fjåge gateway from external applications.
Fire up Unet audio to try out the examples here:
$ bin/unet audio Modem web: http://localhost:8080/
On the shell for the Unet audio SDOAM, check which agents provide the baseband service:
> agentsForService(org.arl.unet.Services.BASEBAND) [phy]
We can now direct all our requests to the
agent. Let’s check the baseband parameters of the
agent (we omit other parameters here for brevity):
> phy « Physical layer » Provides software-defined physical layer communication services (including error detection & correction). [org.arl.unet.bb.BasebandParam] basebandRate ⤇ 12000.0 carrierFrequency = 12000.0 maxPreambleID ⤇ 4 maxSignalLength ⤇ 2147483647 signalPowerLevel = -42.0
We see that the Unet audio SDOAM operates at a carrier frequency of 12 kHz and a bandband sampling rate of 12 kSa/s. Let’s create a DC signal of length 12000 complex baseband samples (24000 floats) and transmit it. A DC signal of length 12000 complex samples with a carrier frequency of 12 kHz is a sinusoidal 12 kHz signal for 1 second.
> s = [1,0]*12000 (1) [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 <<snip>> 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] > s.size() 24000 > phy << new TxBasebandSignalReq(signal: s) AGREE phy >> TxFrameNtf:INFORM[txTime:11401231]
|1||In Groovy, we can repeat a list using the "*" operator. The list [1,0] represents a complex number 1+0j. Repeating it 12000 times gets us a 1 second long DC signal.|
You should hear the sound from your computer speaker.
You can generate other frequency signals using the
Unet audio supports transmission of passband signals as well. Let us create a half second 2000 kHz passband signal and transmit it:
> s = cw(2000, 0.5, 0) (1) > s.size() 48000 (2) > phy << new TxBasebandSignalReq(signal: s, fc: 0) (3) AGREE phy >> TxFrameNtf:INFORM[txTime:414013271]
|2||Notice that our signal is 48000 floats for 0.5 seconds. Compare that with the previous DC signal, which was 24000 floats for 1 second.|
Next, let’s request a recording of 12000 samples (1 second duration):
> phy << new RecordBasebandSignalReq(recLength: 12000) AGREE phy >> RxBasebandSignalNtf:INFORM[adc:1 rxTime:11780353 rssi:-79.1 fc:12000.0 fs:12000.0 (12000 baseband samples)] > ntf.signal (1) [8.611694E-5, 5.8899976E-5, 1.01 <<snip>> E-6, -9.148392E-6, 3.5340495E-6]
You can also ask for recordings to begin at a specified time:
> t = phy.time + 5000000; println(t); phy << new RecordBasebandSignalReq(recLength: 12000, recTime: t) 855949105 AGREE phy >> RxBasebandSignalNtf:INFORM[adc:1 rxTime:855949105 rssi:-90.3 fc:12000.0 fs:12000.0 (12000 baseband samples)]
You’d have noticed the 6 second delay (5 seconds to begin recording, 1 more second to finish the recording) before the recording notification.
Interestingly, you can also request recordings in the past! Many modems have a short buffer, allowing recording in the recent past. Go too far in the past and the modem will refuse your request!
> t = phy.time - 5000000; println(t); phy << new RecordBasebandSignalReq(recLength: 12000, recTime: t) 1186471772 AGREE phy >> RxBasebandSignalNtf:INFORM[adc:1 rxTime:1186471772 rssi:-74.6 fc:12000.0 fs:12000.0 (12000 baseband samples)] > t = phy.time - 60000000; println(t); phy << new RecordBasebandSignalReq(recLength: 12000, recTime: t) 1204946438 REFUSE: Bad start time
Specifying a negative
is understood by the baseband service provider as a relative time. So we can simpify our request to record 5 seconds in the past:
> phy << new RecordBasebandSignalReq(recLength: 12000, recTime: -5000000) AGREE phy >> RxBasebandSignalNtf:INFORM[adc:1 rxTime:1361359020 rssi:-72.9 fc:12000.0 fs:12000.0 (12000 baseband samples)]
16.4. Transmitting and detecting preambles
Each logical channel (CONTROL, DATA, etc.) is associated with a detection preamble. Detectors in a modem monitor incoming signals, and trigger when the preamble is detected.
We can transmit a preamble easily:
> phy << new TxBasebandSignalReq(preamble: 1) AGREE phy >> TxFrameNtf:INFORM[txTime:5470777099]
Here, we did not specify a signal to transmit, so only the preamble was transmitted. You should have heard the preamble as a short chirp from your computer speaker. If we had specified a signal, the preamble would have been followed by the signal.
If you had another Unet audio SDOAM running nearby, it would have heard the preamble and detected a CONTROL frame. It would have tried to decode the frame, but failed, as we didn’t actually transmit anything after the preamble. So you’d have seen a
followed by a
if you had subscribed to
topic on that modem.
If you don’t have access to another computer to run Unet audio on, we can easily demonstrate the above behavior with a single Unet audio SDOAM by simply enabling the full-duplex mode (and hence using the same computer for transmission and reception simultaneously), as we did in Chapter 15 :
> subscribe phy > phy.fullduplex = true true > phy << new TxBasebandSignalReq(preamble:1) AGREE phy >> TxFrameStartNtf:INFORM[txTime:5809832016 txDuration:40416] phy >> TxFrameNtf:INFORM[txTime:5809859766] phy >> RxFrameStartNtf:INFORM[type:CONTROL rxTime:5809825603 rxDuration:2740000 detector:0.9] phy >> BadFrameNtf:INFORM[type:CONTROL rxTime:5809825603 rssi:-55.6 (18 bytes)]
Preambles 1 and 2 are used by the CONTROL and DATA channel respectively. It’s better not to mess with these, but instead use preamble 3, which is left for the user to configure in Unet audio. By default, detection of preamble 3 is disabled. You can enable it by setting the detection threshold
parameter. Let’s try it:
> phy.threshold = 0.25 0.25 > phy.modulation = none none > phy << new TxBasebandSignalReq(preamble: 3) AGREE phy >> TxFrameStartNtf:INFORM[txTime:6011688016 txDuration:170916] phy >> TxFrameNtf:INFORM[txTime:6011686599] phy >> RxFrameStartNtf:INFORM[type:#3 rxTime:6011700270 rxDuration:170500 detector:0.73]
We see the
of type #3 indicating that preamble 3 was detected. Since
is not associated with any modulation scheme (we set the
), the modem did not generate a
as it did with preamble 1.
Preambles 1, 2 and 3 are preconfigured on the Unet audio SDOAM to be short signals with good autocorrelation properties. You can change these, if you wish, by setting the
In applications such as sonar or ranging, we may only be interested in the detecting the timing of a known signal. In that case, the
is sufficient for us. But in some applications, we may wish to capture the signal once detected. That can be easily achieved in Unet audio by setting the
If you enable
, a recording will be triggered every time the preamble is detected:
> phy.basebandRx = true true > phy << new TxBasebandSignalReq(preamble: 3) AGREE phy >> TxFrameStartNtf:INFORM[txTime:6992613349 txDuration:170916] phy >> TxFrameNtf:INFORM[txTime:6992598599] phy >> RxFrameStartNtf:INFORM[type:#3 rxTime:6992616269 rxDuration:170500 detector:0.78] phy >> RxBasebandSignalNtf:INFORM[adc:1 rxTime:6992616269 rssi:-22.0 preamble:3 fc:12000.0 fs:12000.0 (2400 baseband samples)]
notified us of the recorded signal (containing just the detected preamble). If we wanted a longer recording after the preamble, we can ask for that using the
parameter, specifying the length of the recording (in samples) beyond the preamble:
> phy.basebandExtra = 1200 (1) 1200 > phy << new TxBasebandSignalReq(preamble: 3) AGREE phy >> TxFrameStartNtf:INFORM[txTime:7143093349 txDuration:170916] phy >> TxFrameNtf:INFORM[txTime:7143062599] phy >> RxFrameStartNtf:INFORM[type:#3 rxTime:7143081603 rxDuration:1170500 detector:0.78] phy >> RxBasebandSignalNtf:INFORM[adc:1 rxTime:7143081603 rssi:-36.7 preamble:3 fc:12000.0 fs:12000.0 (3600 baseband samples)]
|1||We are requesting 100 ms recording beyond the end of the preamble.|
You can see that the recording is much longer now.
If you cross-correlate this recording with the preamble you transmitted, you’d get an estimate of the impulse response of the channel between your computer speaker and microphone! You can easily obtain the complex baseband representation of preamble #3 that you have been transmitting (
16.5. Baseband signal monitor
During the development of signal processing algorithms, one often wants to simply record received signals in the modem for postprocessing. For this, you could write a script to listen for
agent’s topic, and store them in a file. Since this requirement is common, UnetStack already provides an agent which does exactly this. The agent is called
agent is already loaded when you run Unet audio. However, by default, it is disabled. It’s easy to enable it:
> bbmon.enable = true true
that is sent to
agent’s topic will be recorded in a
file in the
> logs (1) signals-0.txt [0 bytes] (2) results.txt [39 bytes] phy-log-0.txt [687 bytes] log-0.txt [4 kB] > phy << new TxBasebandSignalReq(preamble: 3) (3) AGREE phy >> TxFrameStartNtf:INFORM[txTime:102528016 txDuration:170916] phy >> TxFrameNtf:INFORM[txTime:102513266] phy >> RxFrameStartNtf:INFORM[type:#3 rxTime:102531520 rxDuration:270500 detector:0.74] phy >> RxBasebandSignalNtf:INFORM[adc:1 rxTime:102531520 rssi:-23.6 preamble:3 fc:12000.0 fs:12000.0 (3246 baseband samples)] > logs signals-0.txt [34 kB] (4) results.txt [39 bytes] phy-log-0.txt [687 bytes] log-0.txt [5 kB]
|1||Check the logs folder.|
We have a
Transmit preamble #3. This will trigger a recording, based on the
As you record more signals, they are appended to the same file (with delineating metadata for each signal). If you restart Unet audio, this file will be renumbered to
, as the logs are rotated.
The name of the signals file and number of files kept through log rotation is configured when the
The signals file stores the signals in a base64 encoded format. The Python package
allows you to read this file and work with the signals in it:
$ pip install arlpy (1) $ ipython Python 3.6.8 |Anaconda custom (64-bit)| (default, Dec 29 2018, 19:04:46) Type 'copyright', 'credits' or 'license' for more information IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help. In : from arlpy import unet In : s = unet.get_signals('logs/signals-0.txt') In : s (2) Out: time rxtime adc channels fc ... len preamble rssi filename lno 0 1567961114848 75224853 1 1 0 ... 3246 3 -23.6 logs/signals-0.txt 1 [1 rows x 12 columns] In : x = unet.get_signal(s, 0) In : x.shape (3) Out: (3246,) In : x Out: array([ 9.50995535e-02-3.77136953e-02j, 1.30487725e-01-1.90211199e-02j, 1.27376720e-01+1.91459619e-02j, ..., 8.61445224e-05+2.21590763e-05j, -2.69901575e-05-3.34111392e-05j, 3.39479702e-05-1.82653162e-06j])
16.6. Transmitting and receiving waveforms directly from Python
In the previous section, we showed you how to record signals for postprocessing. This is great if you postprocessing is what you desire, but sometimes it is important to access the functionality in real time from Python. This is very useful while debugging new signal processing algorithms, since tools such as Jupyter notebooks and libraries such as
, and many others have made Python the preferred platform for a lot of scientific computation.
Let’s try it!
You had already installed the Python package
. We’ll be using it now, so in case you don’t have it installed, now is a good time to install it. Start a Jupyter new notebook with Python 3 and connect to your Unet audio instance:
In: from unetpy import * import arlpy.plot as plt In: # connect to the Unet audio SDOAM sock = UnetSocket('localhost', 1100) gw = sock.getGateway() In: # lookup the agent providing baseband service bb = gw.agentForService(Services.BASEBAND) bb.name Out: 'phy' In: # transmit preamble 3 -- you should be able to hear it bb << TxBasebandSignalReq(preamble=3) Out: AGREE In: # discard old notifications to get ready for a recording gw.flush() In: # request a recording bb << RecordBasebandSignalReq() Out: AGREE In: # obtain the recording notification and check that it's of the correct type ntf = gw.receive(timeout=5000) ntf Out: RxBasebandSignalNtf:INFORM[rxTime:203329687 rssi:-68.847565 adc:1 fc:12000.0 fs:12000.0 channels:1 preamble:0 (65536 samples)] In: # close the connection sock.close() In: len(ntf.signal) Out: 65536 In: # plot the first 10000 baseband samples (real/in-phase components only) plt.plot(ntf.signal[:10000].real, fs=ntf.fs) Out:
Of course you could do the same thing with Julia or other languages, if you wish, with obvious minor changes to the syntax!
|<<< [Physical service]||[Ranging and synchronization] >>>|