In the previous notebook, we eventually accomplished to transmit real digital data over our audio channel. However, the payload was fixed and could not be adapted. In this notebook, we will extend the chain to read textual data from a UDP stream and according generate packets. At the receiver side, we will print out the received textual data.
First, let's start with some common imports:
# Source code has been redacted in online version
# Omitting 9 lines of source code
try:
%load_ext tikzmagic
except ModuleNotFoundError:
print ("Did not find tikzmagic. You will not be able to compile the tex code!")
# Source code has been redacted in online version
# Omitting 7 lines of source code
At the beginning, let's write a component that reads from a UDP connection and transforms the received textual data into payload bits. There, each transmitted character corresponds to 8 payload bits. The component sends the payload bits for each frame via its output signal.
# Source code has been redacted in online version
# Omitting 46 lines of source code
Afterwards, the TransmitSignal
component receives the bits and puts them into our designed frame structure. Moreover, when no new payload data is available, the component just creates a dummy packet from a default payload array.
import queue
class TransmitSignal(TX):
def __init__(self, environment, Ts, packeting, defaultPayload):
super().__init__( environment)
self._Ts = Ts
self._packeting = packeting
self._defaultPayload = defaultPayload
self._rxQueue = queue.Queue()
t = np.arange(-5*Ts, 5*Ts, 1/environment._samplerate)
g_samples = get_filter('rc', Ts, rolloff=0.995)(t)
self._filter = StatefulFilter(g_samples, [1])
self._samplerate = environment._samplerate
def receive(self, data):
"""called from the UDP source. Just store the payload in a queue"""
self._rxQueue.put(data.copy())
def __createFrame(self):
# if data is available in the queue, use it. Otherwise, transmit a dummy data 'X'
try:
payload = self._rxQueue.get_nowait()
except queue.Empty:
payload = self._defaultPayload#np.array(int2bin(ord('X'), 8))
packet = self._packeting.createPacket(payload)
# perform BPSK modulation of the bits
frame = 1 - 2.0*packet
return frame
def _generateSignal(self):
Ns = int(self._Ts*self._samplerate)
# get the BPSK symbols and filter them through the TX filter
data = self.__createFrame()
dataUps = np.zeros(int(Ns)*len(data))
dataUps[::Ns] = data
filtered = self._filter.filter(dataUps)
tx = np.real(filtered * 0.2).astype(np.float32)
return tx
These are the new components at the transmitter side. At the receiver, we only need a component to print out the data in text format. In order to do that, it groups the payload into groups of 8 bits and converts each group to a decimal number, which is in turn translated to the corresponding textual symbol. In addition, the component filters out the dummy frames that just contain a single 'X'
(we will define later on that a "null"-frame just contains 'X'
as the payload); instead it prints a simple '.' to stdout to see show that the transmission is still ongoing:
# Source code has been redacted in online version
# Omitting 13 lines of source code
We are now ready to setup the overall transmission chain. Pictorially, it consists of the following components, where the new components are marked in red:
%%tikz -l positioning, --size=800,240
\input{tex/09-1.tex}
def runTransmission(Fc, channelFunc):
samplerate = 44100
env = Environment(samplerate=samplerate)
Ts = 1/(441) # symbol duration
Ns = int(Ts*samplerate)
# 32 bit payload means byte per frame
packeting = Packeting(np.array([1,1,1,0,1,0,0,0,1,0,0,0,1,0]), PayloadLength=32)
defaultPayload = np.array(int2bin(ord('X'), 8))
source = UDPSource(env, nPayloadBytes=4)
transmitter = TransmitSignal(env, Ts, packeting, defaultPayload=defaultPayload)
upconversion = Upconversion(env, Fc=Fc)
channel = channelFunc(env)
downconversion = Downconversion(env, Fc=Fc, B=4/Ts, removeCarrier=True)
timingRecovery = TimingRecovery(env, Ns)
detectFrames = DetectFrames(env, packeting)
printFrames = TextDataSink(env)
plotEye = PlotEyeDiagram(env, Ts=Ts, figsize=(8,3))
source.transmitsTo(transmitter)
transmitter.transmitsTo(upconversion)
upconversion.transmitsTo(channel)
channel.transmitsTo(downconversion)
downconversion.transmitsTo(timingRecovery)
timingRecovery.transmitsTo(detectFrames, stream='samples')
timingRecovery.transmitsTo(plotEye)
detectFrames.transmitsTo(printFrames)
env.run()
Let's run it over the simulated channel:
runTransmission(15000, lambda env: SimulatedChannel(env, channelEffect=HighpassChannelEffect(gain=0.2)))
Hm, the eye diagram looks nice, but no data is actually showing up, only the status dots from the TextDataSink
. That's clear, as we did not yet provide any input data via UDP stream. In order to do that, we can use different tools:
nc -u localhost 50009
. Then type something and press return. The typed data will be printed out to this jupyter notebook.udpsender.py
. Simply run it in a terminal, type something and press return. The typed data will appear as the output of this jupyter notebook.
Let's try again with some data actually being transmitted:runTransmission(15000, lambda env: SimulatedChannel(env, channelEffect=HighpassChannelEffect(gain=0.2)))
Ah, great, now we get the actual data received. I typed Hello
and This is just a test.
at the transmitter side, which was almost succesfully received. Apparently one frame was lost (the one containing ' a t'
between the frames containing 'just'
and 'st '
. Most probably, a bit in the header was corrupted and the decoding was not attempted. We will address this problem in the next notebook.
Now, let's try out the same thing with the audio channel:
runTransmission(15000, AudioChannel)
Yes, also with the audio channel it works (Though, here I used the cabled shortcut connection). Let's look at the received data: 1234 5678 9ABC HI
. At the transmitter, I sent 1234 5678 9ABC DEFG HI
(I artificially inserted the spaces for easier reading). Apparently, one frame was lost during the transmission. We believe the frame loss was due to a bit error in the PHY header, such that the PHY frame was not detected. We will address this problem in the next notebook.
In this notebook, we extended our transmission chain to use variable payload, which can be supplied via a UDP network connection. As a problem we identified that some frames were lost during the transmission. In the following notebook we will address this problem by adding another control layer and repeating each frame several times.
Copyright (C) 2025 - dspillustrations.com