Adding support for a new testbed¶
To add support for a new testbed, the following tasks need to be done:
- Add testbed specific code blocks
Add a module with the name like
spectrumwars.testbed.xxx
. This module should define two classes:Testbed
, a subclass ofTestbedBase
, andRadio
, a subclass ofRadioBase
.
- Add testbed specific unit tests
- Add tests to a Python file with the name like
tests/test_xxx.py
. Please make the tests automatically skip themselves if the testbed-specific hardware is not connected (e.g. raise theunittest.SkipTest
exception) - Add testbed documentation
- Add testbed documentation for players to
docs/reference.rst
. Add any testbed-specific installation instructions todocs/installtestbed.rst
.
Class reference¶
-
class
Testbed
¶ Testbed
objects represent a physical testbed that is used to run a game. Unless stated otherwise, subclasses should override all of the methods and attributes described below.The class constructor can take optional string-typed keyword arguments. These can be specified in
spectrumwars_runner
using the-O
arguments.-
RADIO_CLASS
¶ Should be set to the subclass of the
RadioBase
class that is used by the testbed (i.e.Radio
in the testbed’s module)
-
get_radio_pair
()¶ Returns a
rxradio, txradio
tuple.rxradio
andtxradio
should be instances ofRADIO_CLASS
.This method is called multiple times by the game controller, once for each player to obtain the interfaces to player’s radios. It is called before the game starts, and before the call to
start()
.
-
start
()¶ Called once, immediately before the start of the game.
-
stop
()¶ Called after the game concluded. This method should perform any clean-up tasks required by the testbed (e.g. stopping any threads started by
start()
.
-
get_frequency_range
()¶ Returns the number of frequency channels available to player’s code. The value returned should not change during the lifetime of the object.
Corresponds to
Transceiver.get_frequency_range()
.
-
get_bandwidth_range
()¶ Returns the number of bandwidth settings available to player’s code. The value returned should not change during the lifetime of the object.
Corresponds to
Transceiver.get_bandwidth_range()
.
-
get_power_range
()¶ Returns the number of transmission power settings available to player’s code. The value returned should not change during the lifetime of the object.
Corresponds to
Transceiver.get_power_range()
.
-
get_spectrum
()¶ Returns the current state of the radio spectrum as a list of floating point values.
The value returned by this method gets assigned to
GameStatus.spectrum
.See also
usrp_sensing.SpectrumSensor
.
-
time
()¶ Returns the current testbed time in seconds since epoch as a floating point number. Selection of an epoch does not matter. Game controller requires only that time increases monotonically.
By default it returns
time.time()
, which should be sufficient for most testbeds.
-
-
class
Radio
¶ Radio
objects represent a player’s interface to a single transceiver. Unless stated otherwise, subclasses should override all of the methods described below.-
PACKET_SIZE
¶ Set to the maximum length of a string that can be passed to the
send()
method.Approximately corresponds to
Transceiver.get_packet_size()
. Game controller adds a header to separate control data from payload which adds an overhead of a few bytes. Because of this, the player visible maximum packet size will be lower.
-
set_configuration
(frequency, bandwidth, power)¶ Set up the transceiver for transmission or reception of packets on the specified central frequency, power and bandwidth.
frequency
is specified as channel number from 0 to N-1, where N is the value returned by theTestbed.get_frequency_range()
method.bandwidth
is specified as an integer specifying the radio bitrate and channel bandwidth in the interval from 0 to N-1, where N is the value returned by theTestbed.get_bandwidth_range()
method. Higher values mean higher bitrates and wider channel bandwidths.power
is specified as an integer specifying the transmission power in the interval from 0 to N-1, where N is the value returned by theTestbed.get_power_range()
method. Higher values mean lower power.Corresponds to
Transceiver.set_configuration()
.
-
binsend
(bindata)¶ Send a data packet over the air.
bindata
is a binary string with the data to be included into the packet. Length ofbindata
can be up toPACKET_SIZE
.Corresponds to
Transceiver.send()
. Note that the game controller packs the packet with payload, sobindata
will not be identical to thedata
string passed toTransceiver.send()
.
-
binrecv
(timeout=None)¶ Return a packet from the receive queue.
timeout
specifies the receive timeout in seconds. If no packet is received within the timeout interval, the method raisesRadioTimeout
exception.Upon successfull reception, the method should return a binary string. The returned string should be equal to the
bindata
parameter that was passed to the correspondingsend()
call.Note
There is no way for the
Radio
class to push packets towards the game controller. Instead, the game controller polls the radio for received packets by callingrecv()
method, as instructed by player’s code. Hence it is in most cases necessary that the actual packet reception happens in another thread (started typically fromTestbed.start()
) and that the received packets are held in a queue until the nextrecv()
call.Corresponds to
Transceiver.recv()
. Note that the game controller unpacks the payload from the packet before passing it toTransceiver.recv()
.
-
-
class
usrp_sensing.
SpectrumSensor
(base_hz, step_hz, nchannels, time_window=200e-3, gain=10)¶ usrp_sensing.SpectrumSensor
is a simple, reusable spectrum sensor implementation using a USRP device.The sensing algorithm is inspired by a real-time signal analyzer. The recorded samples are converted into power spectral density using continuous end-to-end FFTs with no blind time (and no overlap of the FFT windows). The spectral power density is then averaged over a time window.
The algorithm is very CPU intensive. Using a 2.7 GHz CPU, it will be able to sense at most 64 channels (even if USRP frontend bandwidth would allow for more).
Sensing in this way is necessary because the radios usually have a very low duty cycle (e.g. a “while True: send()” has only around 10% duty cycle on the VESNA testbed). If we would only take one sample the spectrum when players request it, it would mostly appear empty. Hence the need to take a moving average if sensing is to be useful for detecting player transmissions.
base_hz is the lower bound of the frequency band used in the game in hertz. step_hz is the width of each channel. nchannels is the number of channels used in the game. The values for these parameters should be chosen so that the channel frequencies correspond to the channels used by the testbed’s
Radio
class:-------------------------------> frequency (Hz) +---+---+ +---+ | 0 | 1 | ... | n | (channels used in the game) +---+---+ +---+ |---| <- step_hz |-----------------| <- step_hz * nchannels ^ | base_hz
time_window defines the length of the moving average filter in seconds. The value depends on how often players can look up the current state of the spectrum. In most cases it should be longer than the period of
Transceiver.status_update()
events in the event-based model.-
start
()¶ Start the worker thread. Should be called before first call to
get_spectrum()
-
stop
()¶ Stop the worker thread.
-
get_spectrum
()¶ Returns the current state of the radio spectrum as a list of floating point values. Length of the list is equal to nchannels.
The value returned by this method can be directly used as the return value of
Testbed.get_spectrum()
.
-