Hands-on Digital transmission with your soundcard

This article is part of the fundamentals of my real-world tutorial on digital communications using a cheap soundcard as the radio. If this notebook is interesting to you, check out the full tutorial!

09 - Transmitting custom data

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:

In [1]:
# Source code has been redacted in online version
# Omitting 9 lines of source code
In [2]:
try:
    %load_ext tikzmagic
except ModuleNotFoundError:
    print ("Did not find tikzmagic. You will not be able to compile the tex code!")
In [3]:
%matplotlib notebook
In [4]:
from audioComms import Environment, TX
from audioComms.utils import get_filter, StatefulFilter, get_filter
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, DetectFrames
from audioComms.packeting import Packeting, int2bin, bin2int

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.

In [5]:
# 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.

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

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:

In [7]:
class TextDataSink(Component):
    def __init__(self, environment):
        super().__init__(environment, None)

    def receive(self, packet):
        rxBytes = [bin2int(p) for p in np.split(packet, len(packet) // 8)]
        text = ''.join(chr(x) for x in rxBytes)
                    
        if text != 'X':
            print ("Frame content:", text)
        else:
            sys.stdout.write('.')

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:

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

Let's run it over the simulated channel:

In [16]:
runTransmission(15000, lambda env: SimulatedChannel(env, channelEffect=HighpassChannelEffect(gain=0.2)))
..........Stop received from external

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:

  • On Linux, simply open a terminal and run nc -u localhost 50009. Then type something and press return. The typed data will be printed out to this jupyter notebook.
  • On Windows (or Linux) use the provided tool 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:
In [13]:
runTransmission(15000, lambda env: SimulatedChannel(env, channelEffect=HighpassChannelEffect(gain=0.2)))
......Frame content: Hell
Frame content: o

.....Frame content: This
Frame content:  is 
Frame content: just
Frame content:  a t
Frame content: est

..

Ah, great, now we get the actual data received. I typed Hello and This is just a test. at the transmitter side, which was successfully received.

Now, let's try out the same thing with the audio channel:

In [20]:
runTransmission(15000, AudioChannel)
.............Frame content: 1234
Frame content: 5678
Frame content: 9ABC
Frame content: HI

............

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.

Outlook

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.

This article is part of the fundamentals of my real-world tutorial on digital communications using a cheap soundcard as the radio. If this notebook is interesting to you, check out the full tutorial!

Copyright (C) 2018 - dspillustrations.com


DSPIllustrations.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to amazon.com, amazon.de, amazon.co.uk, amazon.it.