Python Gateway Tutorial

The UnetStack Python gateway API is available via unet-contrib, or from PyPi.

Import unetpy

If you haven't installed unetpy, you need to do that first: pip install unetpy

In [1]:
from unetpy import *

Open a connection to the modem or real-time simulator

For now, we'll assume that we have a modem running on localhost port 1100 (default):

In [2]:
modem = UnetGateway('localhost')

Work with modem parameters

If we are connected to the modem, we can now access the agents and services that the modem provides. Let us try this with the physical layer first. What you'll see here depends on the modem you are using (we are using the portaudio modem on a laptop for this example).

In [3]:
phy = modem.agentForService(Services.PHYSICAL)
In [4]:
phy
Out[4]:
[org.arl.unet.DatagramParam]
  MTU = 13

[org.arl.unet.bb.BasebandParam]
  basebandRate = 12000.0
  carrierFrequency = 12000.0
  maxPreambleID = 4
  signalPowerLevel = -10.0

[org.arl.unet.phy.PhysicalParam]
  busy = False
  maxPowerLevel = 0.0
  minPowerLevel = -138.0
  propagationSpeed = 1500.0
  refPowerLevel = 0.0
  rxEnable = True
  rxSensitivity = 0.0
  time = 17136000
  timestampedTxDelay = 200

[org.arl.unet.scheduler.SchedulerParam]
  rtc = May 26, 2018 8:47:56 PM

[org.arl.yoda.ModemParam]
  bpfilter = True
  diag = none
  fan = False
  fanctl = 45.0
  fullduplex = False
  gain = 0.0
  inhibit = 120
  isc = True
  loopback = False
  model = portaudio
  noise = -79.6
  npulses = 1
  pbsblk = 65536
  pbscnt = 0
  poweramp = True
  preamp = True
  pulsedelay = 0
  serial = portaudio
  vendor = Subnero
  voltage = 0.0

We can query individual parameters or change them:

In [5]:
phy.signalPowerLevel
Out[5]:
-10.0
In [6]:
phy.signalPowerLevel = -6
In [7]:
phy.signalPowerLevel
Out[7]:
-6.0

We can work with the CONTROL (1) or DATA (2) channels too...

In [8]:
phy[1]
Out[8]:
[org.arl.unet.DatagramParam]
  MTU = 13

[org.arl.unet.phy.PhysicalChannelParam]
  dataRate = 125.76419
  errorDetection = True
  fec = 3
  fecList = ['LDPC1', 'LDPC2', 'LDPC3', 'LDPC4', 'LDPC5', 'LDPC6']
  frameDuration = 1.145
  frameLength = 18
  maxFrameLength = 317
  powerLevel = -10.0

[org.arl.yoda.FhbfskParam]
  chiplen = 1
  fmin = 9520.0
  fstep = 384.0
  hops = 13
  tukey = True

[org.arl.yoda.ModemChannelParam]
  basebandExtra = 0
  basebandRx = False
  modulation = fhbfsk
  preamble = org.arl.yoda.Preamble(...)
  test = False
  threshold = 0.3
  valid = True
In [9]:
phy[1].frameLength = 12
In [10]:
phy[1].frameDuration
Out[10]:
0.77

You can also work with higher layers:

In [12]:
link = modem.agentForService(Services.LINK)
In [13]:
link
Out[13]:
[org.arl.unet.DatagramParam]
  MTU = 1552

[org.arl.unet.link.ReliableLinkParam]
  dataChannel = 2
  mac = mac
  maxPropagationDelay = 2.5
  maxRetries = 2
  phy = phy
  reservationGuardTime = 0.5

Send and receive messages

The messages supported on the Python gatweway are pretty much the same as the Java/Groovy messages, with only a slight change in syntax. The package names use underscores (_) instead of dots (.). In Python, the named parameters for message initialization use equals (=) instead of colon (:), and you don't need the new keyword. It's easy to get used to:

In [14]:
phy << org_arl_unet_phy.TxFrameReq(to=2, data=[1,2,3,4])
Out[14]:
AGREE

And read the TxFrameNtf notification once the packet is sent out:

In [15]:
txntf = modem.receive(timeout=2000)

Transmit and receive signals

For this part of the tutorial, we'll use numpy and arlpy. So if you don't have them installed, you'll need them: pip install arlpy (which will also install numpy).

In [16]:
import numpy as np
import arlpy.signal as asig
import arlpy.plot as plt

Generate a passband 100 ms 12 kHz pulse at a sampling rate of 96 kSa/s:

In [17]:
fs = 96000
x = asig.cw(12000, 0.1, fs)

and transmit it using the baseband service:

In [18]:
bb = modem.agentForService(Services.BASEBAND)
bb << org_arl_unet_bb.TxBasebandSignalReq(signal=x.tolist(), fc=0, fs=fs)
Out[18]:
AGREE
In [19]:
txntf = modem.receive(timeout=2000)

By setting fc to 0, we told the modem that this was a passband signal. The sampling rate supported by passband signals will depend on the modem. In our case, the portaudio interface is set to accept 96 kSa/s passband signals.

Now let's ask the modem to record a signal for us:

In [20]:
bb << org_arl_unet_bb.RecordBasebandSignalReq(recLen=4800) 
Out[20]:
AGREE

and wait for a notification for the recorded signal...

In [21]:
rec = modem.receive(org_arl_unet_bb.RxBasebandSignalNtf, timeout=2000)
In [22]:
rec
Out[22]:
RxBasebandSignalNtf:INFORM[(4800 baseband samples)]
In [23]:
rec.fc
Out[23]:
12000
In [24]:
rec.fs
Out[24]:
12000

The notification has 4800 baseband (complex) samples as we had asked, and is sampled at a baseband rate of 12 kSa/s. The carrier frequency used by the modem is 12 kHz. We can convert our recorded signal to passband if we like:

In [25]:
y = asig.bb2pb(rec.signal, rec.fs, rec.fc, fs)
In [26]:
plt.plot(y, fs=fs)
In [27]:
plt.specgram(y, fs=fs)

Clean up

Once we are done, we can clean up by closing the connection to the modem.

In [28]:
modem.shutdown()