I am trying to work on a project involving implementation of acoustic propagation loss models in underwater communication(based on a certain research paper). We are trying to simulate that in unetstack
. The ultimate goal is to create a channel model class that has all the loss models implemented.
But for now we have started by trying to send a baseband signal from one node to another and then are trying to capture the frequency on the receiver node and calculate loss models on that given frequency. (The loss models are a function of frequency value of the signal). I have tried to follow some documentation and some blog posts but I am not able to transmit and receive the signal.
For reference, I have already referred to these articles: 1.) svc-12-baseband 2.) basic-modem-operations-using-unetstack
This is the Research paper that I am following this to calculate the Loss based on different Loss models.
I have tried to write a groovy file for simulation, but it does not seem to work. If someone can please have a look and let me know the mistakes I have made, that would be of real help. We are quite new to unetstack as well as the topic of underwater signal processing like this and this is our first attempt at implementing it on a simulator. We are using unetsim-1.3
Any help is really appreciated! Thanks in advance
import org.arl.fjage.*
import org.arl.unet.*
import org.arl.unet.phy.*
import org.arl.unet.bb.*
import org.arl.unet.sim.*
import org.arl.unet.sim.channels.*
import static org.arl.unet.Services.*
import static org.arl.unet.phy.Physical.*
import java.lang.Math.*
platform = RealTimePlatform
simulate 3.minutes, {
def n = []
n << node('1', address: 1, location: [0,0,0])
n << node('2', address: 2, location: [0,0,0])
n.eachWithIndex { n2, i ->
n2.startup = {
def phy = agentForService PHYSICAL
def node = agentForService NODE_INFO
def bb = agentForService BASEBAND
subscribe phy
subscribe bb
if(node.address == 1)
{
add new TickerBehavior(50000, {
float freq = 5000
float duration = 1000e-3
int fd = 24000
int fc = 24000
int num = duration*fd
def sig = []
(0..num-1).each { t ->
double a = 2*Math.PI*(freq-fc)*t/fd
sig << (int)(Math.cos(a))
sig << (int)(Math.sin(a))
}
bb << new TxBasebandSignalReq(signal: sig)
println "sent"
})
}
if(node.address == 2)
{
add new TickerBehavior(50000, {
bb << new RecordBasebandSignalReq(recLen: 24000)
def rxNtf = receive(RxBasebandSignalNtf, 25000)
if(rxNtf)
{
println "Received"
}
println "Tried"
})
}
}
}
}
In some cases "Tried" is printed first even before "sent" is printed. This shows that (node.address == 2)
code is executing first, before (node.address == 1)
executes.
The basic code you have for transmission (TxBasebandSignalReq
) and reception (RecordBasebandSignalReq
) of signals seems correct.
This should work well on modems, other than the fact that your signal generation is likely flawed for 2 reasons:
You are trying to generate a signal at 5 kHz in baseband representation using a carrier frequency of 24 kHz and a bandwidth of 24 kHz. This signal will be aliased, as this baseband representation can only represent signals of 24±12 kHz, i.e., 12-36 kHz. If you need to transmit a 5 kHz signal, you need your modem to be operating at much lower carrier frequency (easy in the simulator, but in practice you'd need to check your modem specifications).
You are typecasting the output of sin
and cos
to int
. This is probably not what you intended to do, as the signal
is an array of float
scaled between -1 and 1. So just dropping the (int)
would be advisable.
On a simulator, you need to ensure that the modem parameters are setup correctly to reflect your assumptions of baseband carrier frequency, bandwidth and recording length:
modem.carrierFrequency = 24000
modem.basebandRate = 24000
modem.maxSignalLength = 24000
The default HalfDuplexModem
parameters are different, and your current code would fail for RecordBasebandSignalReq
with a REFUSE
response (which your code is not checking).
The rest of your code looks okay, but I'd simplify it a bit to:
import org.arl.fjage.*
import org.arl.unet.bb.*
import org.arl.unet.Services
platform = RealTimePlatform
modem.carrierFrequency = 24000
modem.basebandRate = 24000
modem.maxSignalLength = 48000
simulate 3.minutes, {
def n1 = node('1', address: 1, location: [0,0,0])
def n2 = node('2', address: 2, location: [0,0,0])
n1.startup = {
def bb = agentForService Services.BASEBAND
add new TickerBehavior(50000, {
float freq = 25000 // pick a frequency in the 12-36 kHz range
float duration = 1000e-3
int fd = 24000
int fc = 24000
int num = duration*fd
def sig = []
(0..num-1).each { t ->
double a = 2*Math.PI*(freq-fc)*t/fd
sig << Math.cos(a)
sig << Math.sin(a)
}
bb << new TxBasebandSignalReq(signal: sig)
println "sent"
})
}
n2.startup = {
def bb = agentForService Services.BASEBAND
add new TickerBehavior(50000, {
bb << new RecordBasebandSignalReq(recLen: 24000)
def rxNtf = receive(RxBasebandSignalNtf, 25000)
if(rxNtf) {
println "Received"
}
println "Tried"
})
}
}
This should work as expected!
However, there are a few more gotchas to bear in mind:
You are sending and recording on a timer. On a simulator, this should be okay, as both nodes have the same time origin and no propagation delay (you've setup the nodes at the same location). However, on a real modem, the recording may not be happening when the transmission does.
Transmission and reception of signals with a real modem works well. The Unet simulator is primarily a network simulator and focuses on simulating the communication system behavior of modems, but not necessarily the acoustic propagation. While it supports the BASEBAND
service, the channel physics of transmitting signals is not accurately modeled by the default HalfDuplexModem
model. So your mileage on signal processing the recording may vary. This can be fixed by defining your own channel model that uses an appropriate acoustic propagation model, but is a non-trivial undertaking.