# 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

dataChannel = 2
mac = mac
maxPropagationDelay = 2.5
maxRetries = 2
phy = phy
reservationGuardTime = 0.5


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)


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()