In the previous notebook, we integrated our physical layer with a packeting scheme and a data source that was fed by a UDP network connection. With this setup, we were able to transmit arbitrary data over the audio channel. However, we experienced problems that some frames were lost, either due to too many errors or due to an error in the PHY frame header.
In this notebook, we will address this problem by repeatedly transmitting the same frame multiple times. However, in order to not receive multiple copies of the same data at the application layer, a duplicate detection and omission needs to be implemented in between.
Let's start with the default imports:
import matplotlib import matplotlib.pyplot as plt %matplotlib notebook %load_ext autoreload %autoreload 2 import numpy as np import sys; sys.path.append('..')
try: %load_ext tikzmagic except ModuleNotFoundError: print ("Did not find tikzmagic. You will not be able to compile the tex code!")
from audioComms import Environment, TX from audioComms.utils import get_filter, StatefulFilter from audioComms.buffers import RingBuffer from audioComms.channels import AudioChannel, SimulatedChannel, IdentityChannelEffect, HighpassChannelEffect from audioComms.plotting import PlotEyeDiagram from audioComms.components import Component, Upconversion, Downconversion, TimingRecovery from audioComms.components import DetectFrames, UDPSource, PacketizedBPSKTransmitter, TextDataSink from audioComms.packeting import Packeting, int2bin, bin2int
Our strategy to overcome the problem with losing frames is to transmit multiple copies of each frame, hoping that at least one repetition gets through successfully. However, we need to add some logic to identify duplicated frames at the receiver, such that we do not forward the same information twice. Therefore, each frame gets an ID, the so-called
frameCounter, which corresponds to the first 8 bits in the payload.
Let's write a component, which takes data and packs it into a frame with a frame ID. Then, this frame is repeated 3 times. In addition,
frameCounter==0 denotes an empty or dummy frame, where no payload would be contained.
class RepeatAndAddFrameCounter(Component): def __init__(self, environment): super().__init__(environment, None) self._frameCounter = 1 def receive(self, payload): # add the frame counter index to the frame frame = np.hstack([int2bin(self._frameCounter, 8), payload]) # wrap around counter at 256 self._frameCounter = max(1, (self._frameCounter+1) % 256) # transmit the same frame 3 times for i in range(3): self._send(frame)
At the receiver, we implement the counterpart: For each received frame, we decode the frame counter, and only forward the payload, if the decoded counter is different from the previous frame ID:
class DecodeFrameCounter(Component): def __init__(self, environment): super().__init__(environment, None) self._lastFrameCounter = 0 def receive(self, packet): frameCounter = bin2int(packet[:8]) # decode the frame counter # check, if frame counter has changed from last packet to current if frameCounter != self._lastFrameCounter and frameCounter > 0: # it has changed, so forward the contained payload self._send(packet[8:]) self._lastFrameCounter = frameCounter
These two additional components now allow us to finish our data transmission chain. Pictorially, we have the following components:
# Source code has been redacted in online version # Omitting 35 lines of source code
def runTransmission(Fc, channelFunc): samplerate = 44100 env = Environment(samplerate=samplerate) Ts = 1/(2*441) # symbol duration Ns = int(Ts*samplerate) # 32 bit payload means 4 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.zeros(8) source = UDPSource(env, nPayloadBytes=3) # use 3 bytes payload, as 1 byte is for the counter repeater = RepeatAndAddFrameCounter(env) transmitter = PacketizedBPSKTransmitter(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) decodeFrames = DecodeFrameCounter(env) printFrames = TextDataSink(env) plotEye = PlotEyeDiagram(env, Ts=Ts, figsize=(8,3)) source.transmitsTo(repeater) repeater.transmitsTo(transmitter) transmitter.transmitsTo(upconversion) upconversion.transmitsTo(channel) channel.transmitsTo(downconversion) downconversion.transmitsTo(timingRecovery) timingRecovery.transmitsTo(detectFrames, stream='samples') timingRecovery.transmitsTo(plotEye) detectFrames.transmitsTo(decodeFrames) decodeFrames.transmitsTo(printFrames) env.run()
First, let's try out the chain in the simulated channel: As in the previous notebook, send data via UDP to the chain:
runTransmission(15000, lambda env: SimulatedChannel(env, channelEffect=HighpassChannelEffect(gain=0.2)))
Frame data: Hi Frame data: ABC Frame data: DEF Frame data: GHI Frame data: JKL Frame data: MNO Frame data: PQR Frame data: STU Frame data: VWX Frame data: YZ Stop received from external
In above example, I send first the text
Hi to the chain. Then, I sent
ABCDEFGHIJKLMNOPQRSTUVWXYZ. As we see, all bytes are successfully received and decoded. No frames were lost. Moreover, no duplicates are shown, meaning that the duplicate detection worked successfully. Now, let's eventually try out the transmission over the real audio channel:
Frame data: ABC Frame data: DEF Frame data: GHI Frame data: JKL Frame data: MNO Frame data: PQR Frame data: STU Frame data: VWX Frame data: YZ
Again, we received all frames and no frames were lost. Hence, we have successfully mitigated the problem of losing frames due to the erroneous header bits. However, unquestionably, the transmission efficiency is very low, as we have to transmit every frame 3 times.
We have now reached the end of this little toy example of a digital transmission system. Throughout the course, we have gained some hands-on knowledge on the following points:
It is clear, that the presented techniques are not optimized for efficiency. For example, the employed repetition code is very weak, and the frame detection based on the header is very error-prone. Instead, we used the simplest methods possible to achieve a reliable transmission.
Moreover, throughout the course, we did not focus on mathematics, but only on applications. For more information about the theoretical background, see e.g. the articles at http://dspillustrations.com.