Getting started
The PHY domain is dedicated to low-level modulation and demodulation. This domain allows sending a series of bytes from the host to any wireless device as well as receiving bytes sent by any wireless device with a compatible WHAD interface.
The following modulation/demodulation are supported by the WHAD protocol, but not by all WHAD adapters:
Amplitude Shift Keying / On/Off Keying (ASK / OOK)
Frequency Shift Keying (FSK)
Four Frequency Shift Keying (4FSK)
Gaussian Frequency Shift Keying (GFSK)
Minimum-Shift Keying (MSK)
Quadrature Phase-Shift Keying (QPSK)
Binary Phase-Shift Keying (BPSK)
LoRa
Sniffing raw packets
The whad.phy.connector.sniffer.Sniffer
class wraps the demodulating
logic and must be used to sniff raw demodulated data. This sniffer must be
configured through a dedicated whad.phy.sniffing.SnifferConfiguration
instance that will determine the demodulation and its parameters.
First, we need to create an instance of this class to hold our mod/demod parameters:
from whad.device import WhadDevice
from whad.phy import SnifferConfiguration, Sniffer
# Create our WHAD device
device = WhadDevice.create('uart:/dev/ttyUSB0')
# Create our sniffing configuration
config = SnifferConfiguration()
Setting the target frequency
Setting the target frequency (in Hertz) is pretty simple:
# Configure demodulation
config.frequency = 2402000000
In our case, we are going to sniff on 2.402 GHz (2.402.000.000 Hz).
Setting the modulation and its parameters
Modulation is specified by setting the correct boolean in SnifferConfiguration
.
In our case, we want to use a GFSK demodulation so we set the gfsk
property
to True
:
# Configure demodulation
config.frequency = 2402000000
config.gfsk = True
Frequency-shift keying modulation requires more parameters to be set, including:
deviation (in Hertz): this value indicates the frequency deviation
datarate (in bits per second): this value specifies the communication speed
We want to demodulate a signal with a deviation of 250 kHz with a datarate of 1 Mbps:
# Configure demodulation
config.frequency = 2402000000
config.gfsk = True
config.deviation = 250000
config.datarate = 1000000
Setting endianness and size
During digital demodulation, encoded bits are grouped by 8 to form bytes and these bytes are grouped to form a raw packet. We need to tell the demodulator the bit order to use (either little-endian or big-endian). Little-endian indicates that the least-significant bit (LSB) is sent first while big-endian indicates that the most-significant bit (MSB) is sent first. Last but not least, we must give the demodulator a maximum packet size as this latter is not able to stream raw demodulated data (but only chunks of data stored in packets).
We want bytes with LSB first, for a maximum size of 128 bytes per packet:
# Configure demodulation
config.frequency = 2402000000
config.gfsk = True
config.datarate = 1000000
config.little_endian = True
config.fsk_configuration.deviation = 250000
config.packet_size = 128
Setting synchronization word
Usually, a demodulator looks for a specific series of bits that are expected to mark the beginning of a frame or a packet. This series of bits is called a synchronization word, or sync word in short. It can be configured as well, but by default, it is set to a byte made of atlernating 0s and 1s: 0xAA.
We can use another synchronization word, like 0xF00D:
# Configure demodulation
config.frequency = 2402000000
config.gfsk = True
config.datarate = 1000000
config.little_endian = True
config.fsk_configuration.deviation = 250000
config.packet_size = 128
config.sync_word = b"\x0D\xF0" # little-endian
Sniffing demodulated raw packets
When all parameters are correctly set, the configuration can be passed to our sniffer in order to demodulate data:
from whad.device import WhadDevice
from whad.phy import SnifferConfiguration, Sniffer
# Create our WHAD device
device = WhadDevice.create('uart:/dev/ttyUSB0')
# Create our sniffing configuration
config = SnifferConfiguration()
# Configure demodulation
config.frequency = 2402000000
config.gfsk = True
config.datarate = 1000000
config.little_endian = True
config.fsk_configuration.deviation = 250000
config.packet_size = 128
config.sync_word = b"\x0D\xF0" # little-endian
# Create a sniffer based on our configuration
sniffer = Sniffer(device)
sniffer.configuration = configuration
Last but not least, we ask our sniffer to capture packets and report them:
# Start sniffer
sniffer.start()
# Demodulate raw packets
for packet in sniffer.sniff():
packet.show()
Sending raw packets
Raw packets can be sent pseudo-synchronously or asynchronously, depending on the need. Pseudo-synchronous packets are sent as soon as possible, depending on the hardware and the latency caused by the host/interface communication. Asynchronous packets are far more precise but need to be prepared and fed into the hardware enough time before sending.
In order to transmit a raw packet, we need to use the default whad.phy.connector.PHY
class and set the modulation parameters as we did above with our sniffer. For
instance, to send GFSK raw packets with a sync word of 0xF00D on 2.402 GHz,
a deviation of 250 kHz and a datarate of 1 Mbps, we use the following code:
from whad.device import WhadDevice
from whad.phy import Phy, endianness
# Create our whad device
device = WhadDevice.create("uart0")
# We wrap it with our connector
phy = PHY(device)
# Configure our transmitter
phy.set_frequency(2402000000)
phy.set_gfsk(deviation=250000)
phy.set_datarate(1000000)
phy.set_endianness(Endianness.LITTLE)
phy.set_sync_word(b"\x0D\x0F)
Pseudo-synchronous packet transmission
To send a pseudo-synchronous packet, we simply use the whad.phy.connector.PHY.send()
function:
phy.send(b"Hello, World !")
Warning
There is no certainty the hardware will send the packet as soon as it receives the command corresponding to the packet transmission, but it will do its best to send it as soon as possible.
This is a known limitation of WHAD’s design that can be solved with scheduled packets (see below).
Asynchronous packet transmission
It is also possible to tell the hardware when to send a packet with a precision of about a microsecond by using a scheduled packet. Scheduled packets are packets that are sent ahead of the real transmission, saved into the hardware and sent at the expected time with no delay.
To send such a packet, we need to prepare it and send it to our WHAD device:
phy.schedule_send(b"Hello, World!", timestamp=1515.0)
The hardware may have a limited number of slots for scheduled packets and this
code may raise the whad.phy.exceptions.ScheduleFifoFull
exception if
all the slots are already set.
Packet will be transmitted at the specified timestamp and a notification sent to the connector.