# Hands-on Digital transmission with your soundcard¶

## 08 - Our first end-to-end data transmission¶

In the previous notebook, we designed a simple frame structure for aligning data into packets which can be decoded independently. The frame consisted of a header and a payload, where the payload was protected with a channel code and a checksum to ensure successful detection. We implemented the packet creating and decoding in a Packeting class.

In this notebook, we will use this class and all the previous blocks to create an end-to-end digital transmission using your soundcard. However, in this notebook, we will keep the transmitted data constant. We will extend the system to textual data received from a UDP stream in the next notebook.

First, let's have some standard imports:

In [1]:
# Source code has been redacted in online version
# Omitting 9 lines of source code

In [2]:
try:
except ModuleNotFoundError:
print ("Did not find tikzmagic. You will not be able to compile the tex code!")

In [3]:
%matplotlib notebook

In [4]:
# Source code has been redacted in online version
# Omitting 7 lines of source code


Then, let's define our transmitter. For now, we fix the payload it transmits to a given value of [1,1,1,1,0,0,0,0,1,1,0,0,1,0,1,0]. The principle is simple: Whenever the transmitter is asked to generate a transmit signal, it creates a frame with the constant payload given in the createFrame method.

In [5]:
class TransmitSignal(TX):
def __init__(self, environment, Ts, packeting):
super().__init__( environment)
self._Ts = Ts
self._packeting = packeting

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 __createFrame(self):
packet = self._packeting.createPacket(np.array([1,1,1,1,0,0,0,0,1,1,0,0,1,0,1,0]))

# 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


At the receiver side, we create a component which reads a stream of bits and attempts to decode the packets from the stream. In order to do this, for each received bit the decoder is run on the last N bits, where N is the length of one PHY packet. If the decoder found a packet, the contained payload data is forwarded to the next component:

In [6]:
class DetectFrames(Component):
def __init__(self, environment, packeting):
super().__init__(environment, None)
self._buffer = RingBuffer(packeting._packetLength, dtype=np.int8)
self._packeting = packeting

bits = np.sign(data) == -1
for b in bits:
d = self._buffer.data()
packet = self._packeting.decodePacket(d)
if packet is not None:
self._send(packet)


As the data sink, we simple write a component that just prints out the bits in a received frame:

In [7]:
# Source code has been redacted in online version
# Omitting 6 lines of source code


Already now, we can set up our transmission chain. Within the chain, we use all the blocks from the previous notebooks: Upconversion, Downconversion and Timing Recovery. Pictorially, we have the following connections, with the blocks relevant from this notebook marked:

In [8]:
# Source code has been redacted in online version
# Omitting 29 lines of source code

In [9]:
# Source code has been redacted in online version
# Omitting 28 lines of source code


Let's try our system in the simulated channel. If everything works well, the PrintFrames component should print frames with exactly the payload we setup in the TransmitSignal component.

In [10]:
runTransmission(18000, lambda env: SimulatedChannel(env, channelEffect=HighpassChannelEffect(gain=0.2)))

Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]


Great! After some settling of the internal gain parameters, the eye diagram stabilizes and the receiver prints out the expected payload! We have successfully created a rudimentary digital transmission system!

Let's try the system in the real audio channel:

In [16]:
runTransmission(8000, AudioChannel)

Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
Received Frame bits: [1 1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]


Yeah! It also works over the audio channel. After moving the microphone towards the speaker and letting the gains settle, the eye diagram stabilizes. Then, the frames are correctly detected!

### Summary¶

In this notebook, we have successfully integrated our packet encoding and decoding into the transmission system. With this packeting mechanism, we are now able to really transmit digital information over our audio channel.

In the next notebook, we will extend this system to receive data from a UDP network stream, such that we can control the transmitted payload on the fly.