{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lab 5 Part IV: Stream processing and full APRS tranciever\n", "\n", "\n", "In this part of the lab we will take the functions that we wrote in the previous part and put them in a streaming architecture such that we can collect finite buffers and proces them in real-time. \n", "\n", "You will need the file `ax25.py`\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%pylab\n", "# Import functions and libraries\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import pyaudio\n", "import Queue\n", "import threading,time\n", "import sys\n", "\n", "from numpy import pi\n", "from numpy import sin\n", "from numpy import zeros\n", "from numpy import r_\n", "from numpy import ones\n", "from scipy import signal\n", "from scipy import integrate\n", "\n", "import threading,time\n", "import multiprocessing\n", "\n", "from numpy import mean\n", "from numpy import power\n", "from numpy.fft import fft\n", "from numpy.fft import fftshift\n", "from numpy.fft import ifft\n", "from numpy.fft import ifftshift\n", "import bitarray\n", "from scipy.io.wavfile import read as wavread\n", "import serial\n", "import ax25\n", "from fractions import gcd\n", "\n", "\n", "\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# function to compute least common multipler\n", "def lcm(numbers):\n", " return reduce(lambda x, y: (x*y)/gcd(x,y), numbers, 1)\n", "\n", "\n", "def play_audio( Q,ctrlQ ,p, fs , dev, ser=\"\", keydelay=0.1):\n", " # play_audio plays audio with sampling rate = fs\n", " # Q - A queue object from which to play\n", " # ctrlQ - A queue object for ending the thread\n", " # p - pyAudio object\n", " # fs - sampling rate\n", " # dev - device number\n", " # ser - pyserial device to key the radio\n", " # keydelay - delay after keying the radio\n", " #\n", " #\n", " # There are two ways to end the thread: \n", " # 1 - send \"EOT\" through the control queue. This is used to terminate the thread on demand\n", " # 2 - send \"EOT\" through the data queue. This is used to terminate the thread when data is done. \n", " #\n", " # You can also key the radio either through the data queu and the control queue\n", " \n", " \n", " # open output stream\n", " ostream = p.open(format=pyaudio.paFloat32, channels=1, rate=int(fs),output=True,output_device_index=dev)\n", " # play audio\n", " while (1):\n", " if not ctrlQ.empty():\n", " \n", " # control queue \n", " ctrlmd = ctrlQ.get()\n", " if ctrlmd is \"EOT\" :\n", " ostream.stop_stream()\n", " ostream.close()\n", " print(\"Closed play thread\")\n", " return;\n", " elif (ctrlmd is \"KEYOFF\" and ser!=\"\"):\n", " ser.setDTR(0)\n", " #print(\"keyoff\\n\")\n", " elif (ctrlmd is \"KEYON\" and ser!=\"\"):\n", " ser.setDTR(1) # key PTT\n", " #print(\"keyon\\n\")\n", " time.sleep(keydelay) # wait 200ms (default) to let the power amp to ramp up\n", " \n", " \n", " data = Q.get()\n", " \n", " if (data is \"EOT\") :\n", " ostream.stop_stream()\n", " ostream.close()\n", " print(\"Closed play thread\")\n", " return;\n", " elif (data is \"KEYOFF\" and ser!=\"\"):\n", " ser.setDTR(0)\n", " #print(\"keyoff\\n\")\n", " elif (data is \"KEYON\" and ser!=\"\"):\n", " ser.setDTR(1) # key PTT\n", " #print(\"keyon\\n\")\n", " time.sleep(keydelay) # wait 200ms (default) to let the power amp to ramp up\n", " \n", " else:\n", " try:\n", " ostream.write( data.astype(np.float32).tostring() )\n", " except:\n", " print(\"Exception\")\n", " break\n", " \n", "def record_audio( queue,ctrlQ, p, fs ,dev,chunk=1024):\n", " # record_audio records audio with sampling rate = fs\n", " # queue - output data queue\n", " # p - pyAudio object\n", " # fs - sampling rate\n", " # dev - device number \n", " # chunk - chunks of samples at a time default 1024\n", " #\n", " # Example:\n", " # fs = 44100\n", " # Q = Queue.queue()\n", " # p = pyaudio.PyAudio() #instantiate PyAudio\n", " # record_audio( Q, p, fs, 1) # \n", " # p.terminate() # terminate pyAudio\n", " \n", " \n", " istream = p.open(format=pyaudio.paFloat32, channels=1, rate=int(fs),input=True,input_device_index=dev,frames_per_buffer=chunk)\n", "\n", " # record audio in chunks and append to frames\n", " frames = [];\n", " while (1):\n", " if not ctrlQ.empty():\n", " ctrlmd = ctrlQ.get() \n", " if ctrlmd is \"EOT\" :\n", " istream.stop_stream()\n", " istream.close()\n", " print(\"Closed record thread\")\n", " return;\n", " try: # when the pyaudio object is distroyed stops\n", " data_str = istream.read(chunk) # read a chunk of data\n", " except:\n", " break\n", " data_flt = np.fromstring( data_str, 'float32' ) # convert string to float\n", " queue.put( data_flt ) # append to list\n", "\n", " \n", " \n", "\n", "def printDevNumbers(p):\n", " N = p.get_device_count()\n", " for n in range(0,N):\n", " name = p.get_device_info_by_index(n).get('name')\n", " print n, name\n", " \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ " \n", "p = pyaudio.PyAudio()\n", "printDevNumbers(p)\n", "p.terminate()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# CHANGE!!!!\n", "dusb_in = 3\n", "dusb_out = 3\n", "din = 0\n", "dout = 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Initialize serial port" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "if sys.platform == 'darwin': # Mac\n", " s = serial.Serial(port='/dev/tty.SLAB_USBtoUART')\n", "else: #windows\n", " s = serial.Serial(port='COM1') # CHANGE !!!!!!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating a TNC (Termina Node Controller, e.g., modem) class\n", "\n", "The architecture we chose for this lab is to create a class that implements the functions of the modem, while keeping joint state variables such that it would be possible to process buffers in real-time while maintaining continuity and integrity of the processing as if we are processing a large buffer. \n", "\n", "\n", "We created the infrastructure for you, implemented in the `TNCaprs` class. \n", "It implelents an overlapp and save approach. The constructor for the class takes in the sampling frequency: `fs` the size of pyAudio buffer: `Abuffer` and the number of audio buffers to collect before processing:`Nchunks`\n", "\n", "\n", "##### `processBuffer(self, buff_in)`\n", "The method `processBuffer(buff_in)` takes buffers in to be processed, collects them into a large buffer made of `Nchunks`. Once the large buffer has been collected, the method calls the demodulation method to create an NRZI, it calls the PLL to find the sampling timings, it samples the NRZI and converts it to NRZ bits. Finally it calls the function that looks for packets in the bitstream. \n", "The method implements an overlapp and save approach. So, it calls the demodulation function with a buffer that is overlaping and expects a smaller buffer, that contains only valid linear convolution samples. \n", "\n", "##### `demod(self, buff)` -- same as `nc_afsk1200Demod`\n", "Because there are 3 filters one after the other in the demodulator, each one of length N, the method `processBuffer(buff_in)` sends to `demod` a buffer sized `Nchunks*Abuffer + 3*(N-1)` and expects `Nchunks*Abuffer` samples in return. If you have different size filters, you need to modify `processBuffer(buff_in)` to account for that. The filters for `demod` are generated in the `__init__` function of the `TNCaprs` class.\n", "\n", "##### ` PLL(self, NRZa)`\n", "This is the same PLL implementation as before. The only different is that the PLL counter: `pll`, its previous value: `ppll`, the stepsize: `dpll` and the agressivness or scaling factor `apll` are all class variables. This way, when the PLL finishes processing a buffer, the valuse are saved and used in the beginning of the next buffer\n", "\n", "##### `findPackets(self,bits)`\n", "This function is the same as before. The only differences are that the variables, `state`, `pktcounter`, and `packet` are class variable and their value is kept from one call to another. \n", "\n", "Another difference is that we also added another variable: `bitpointer`. As you recall, the function looks for flags in the bitstream up to 7 bits before the end of the buffer. `bitpointer` is needed for the case where a flag is detected at the end of the buffer and extend to these 7 bits. That means that when we process the next buffer, we need to start after those bits, and `bitpointer` points to the bit we need to start with.\n", "\n", "##### `modulate(self, bits)` -- same as `afsk1200`\n", "Function takes bits and afsk 1200 modulates them. The sampling rate is initialized in `TNCaprs.__init__`\n", "\n", "##### `modulatPacket(self, callsign, digi, dest, info, preflags=80, postflags=80 )`\n", " \n", "Given callsign, digipath, dest, info, number of pre-flags and post-flags the function contructs an appropriate aprs packet, then converts them to NRZI and calls `modulate` to afsk 1200 modulate the packet. \n", "\n", "\n", "#### Task: \n", "* Convers your functions `nc_afsk1200Demod` and `afsk1200` to `TNCaprs.demod` and `TNCaprs.modulate`. Make it into class form in which class vriables are in the form of `self.variable` which are initialized in `TNCaprs.__init__`. \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class TNCaprs:\n", " \n", " def __init__(self, fs = 48000.0, Abuffer = 1024, Nchunks=43):\n", " \n", " # Implementation of an afsk1200 TNC. \n", " #\n", " # The TNC processes a `Abuffer` long buffers, till `Nchunks` number of buffers are collected into a large one.\n", " # This is because python is able to more efficiently process larger buffers than smaller ones.\n", " # Then, the resulting large buffer is demodulated, sampled and packets extracted.\n", " #\n", " # Inputs:\n", " # fs - sampling rate\n", " # TBW - TBW of the demodulator filters\n", " # Abuffer - Input audio buffers from Pyaudio\n", " # Nchunks - Number of audio buffers to collect before processing\n", " # plla - agressivness parameter of the PLL\n", " \n", " \n", " ## compute sizes based on inputs\n", " self.TBW = 2.0 # TBW for the demod filters\n", " self.N = (int(fs/1200*self.TBW)//2)*2+1 # length of the filters for demod\n", " self.fs = fs # sampling rate \n", " self.BW = self.TBW/(1.0*self.N/fs) # BW of filter based on TBW\n", " self.Abuffer = Abuffer # size of audio buffer\n", " self.Nchunks = Nchunks # number of audio buffers to collect\n", " self.Nbuffer = Abuffer*Nchunks+self.N*3-3 # length of the large buffer for processing\n", " self.Ns = 1.0*fs/1200 # samples per symbol\n", " \n", " ## state variables for the modulator\n", " self.prev_ph = 0 # previous phase to maintain continuous phase when recalling the function\n", " \n", " ## Generate Filters for the demodulator\n", " self.h_lp = \n", " self.h_space = \n", " self.h_mark = \n", " \n", " self.h_lpp = \n", " self.h_bp = \n", "\n", "\n", " ## PLL state variables -- so conntinuity between buffers is preserved\n", " self.dpll = np.round(2.0**32 / self.Ns).astype(int32) # PLL step\n", " self.pll = 0 # PLL counter\n", " self.ppll = -self.dpll # PLL counter previous value -- to detect overflow\n", " self.plla = 0.74 # PLL agressivness (small more agressive)\n", " \n", "\n", " ## state variable to NRZI2NRZ\n", " self.NRZIprevBit = True \n", " \n", " ## State variables for findPackets\n", " self.state='search' # state variable: 'search' or 'pkt'\n", " self.pktcounter = 0 # counts the length of a packet\n", " self.packet = bitarray.bitarray([0,1,1,1,1,1,1,0]) # current packet being collected\n", " self.bitpointer = 0 # poiter to advance the search beyond what was already searched in the previous buffer\n", "\n", " ## State variables for processBuffer\n", " self.buff = zeros(self.Nbuffer) # large overlapp-save buffer\n", " self.chunk_count = 0 # chunk counter\n", " self.oldbits = bitarray.bitarray([0,0,0,0,0,0,0]) # bits from end of prev buffer to be copied to beginning of new\n", " self.Npackets = 0 # packet counter\n", " \n", " \n", " \n", " \n", " def NRZ2NRZI(self,NRZ, prevBit = True):\n", " NRZI = NRZ.copy() \n", " for n in range(0,len(NRZ)):\n", " if NRZ[n] :\n", " NRZI[n] = prevBit\n", " else:\n", " NRZI[n] = not(prevBit)\n", " prevBit = NRZI[n]\n", " return NRZI\n", "\n", " def NRZI2NRZ(self, NRZI): \n", " NRZ = NRZI.copy() \n", " \n", " for n in range(0,len(NRZI)):\n", " NRZ[n] = NRZI[n] == self.NRZIprevBit\n", " self.NRZIprevBit = NRZI[n]\n", " \n", " return NRZ\n", " \n", " def modulate(self,bits):\n", " # the function will take a bitarray of bits and will output an AFSK1200 modulated signal of them, sampled at 44100Hz\n", " # Inputs:\n", " # bits - bitarray of bits\n", " # fs - sampling rate\n", " # Outputs:\n", " # sig - returns afsk1200 modulated signal\n", " \n", "\n", " \n", " # for you to complete\n", " \n", " return sig \n", " \n", " def modulatPacket(self, callsign, digi, dest, info, preflags=80, postflags=80 ):\n", " \n", " # given callsign, digipath, dest, info, number of pre-flags and post-flags the function contructs\n", " # an appropriate aprs packet, then converts them to NRZI and calls `modulate` to afsk 1200 modulate the packet. \n", " \n", " packet = ax25.UI(destination=dest,source=callsign, info=info, digipeaters=digi.split(b','),)\n", " prefix = bitarray.bitarray(np.tile([0,1,1,1,1,1,1,0],(preflags,)).tolist())\n", " suffix = bitarray.bitarray(np.tile([0,1,1,1,1,1,1,0],(postflags,)).tolist())\n", " sig = self.modulate(self.NRZ2NRZI(prefix + packet.unparse()+suffix))\n", "\n", " return sig\n", " \n", " \n", "\n", " def demod(self, buff):\n", " # Similar to afsk1200_demod, for you to complete\n", " \n", " \n", " return NRZ\n", "\n", "\n", " \n", " def PLL(self, NRZa): \n", " idx = zeros(len(NRZa)//int(self.Ns)*2) # allocate space to save indexes \n", " c = 0\n", " \n", " for n in range(1,len(NRZa)):\n", " if (self.pll < 0) and (self.ppll >0):\n", " idx[c] = n\n", " c = c+1\n", " \n", " if (NRZa[n] >= 0) != (NRZa[n-1] >=0):\n", " self.pll = int32(self.pll*self.plla)\n", " \n", " \n", " self.ppll = self.pll\n", " self.pll = int32(self.pll+ self.dpll)\n", " \n", " return idx[:c].astype(int32) \n", " \n", " \n", "\n", " def findPackets(self,bits):\n", " # function take a bitarray and looks for AX.25 packets in it. \n", " # It implements a 2-state machine of searching for flag or collecting packets\n", " flg = bitarray.bitarray([0,1,1,1,1,1,1,0])\n", " packets = []\n", " n = self.bitpointer\n", " \n", " # Loop over bits\n", " while (n < len(bits)-7) :\n", " # default state is searching for packets\n", " if self.state is 'search':\n", " # look for 1111110, because can't be sure if the first zero is decoded\n", " # well if the packet is not padded.\n", " if bits[n:n+7] == flg[1:]:\n", " # flag detected, so switch state to collecting bits in a packet\n", " # start by copying the flag to the packet\n", " # start counter to count the number of bits in the packet\n", " self.state = 'pkt'\n", " self.packet=flg.copy()\n", " self.pktcounter = 8\n", " # Advance to the end of the flag\n", " n = n + 7\n", " else:\n", " # flag was not found, advance by 1\n", " n = n + 1 \n", " \n", " # state is to collect packet data. \n", " elif self.state is 'pkt':\n", " # Check if we reached a flag by comparing with 0111111\n", " # 6 times ones is not allowed in a packet, hence it must be a flag (if there's no error)\n", " if bits[n:n+7] == flg[:7]:\n", " # Flag detected, check if packet is longer than some minimum\n", " if self.pktcounter > 200:\n", " # End of packet reached! append packet to list and switch to searching state\n", " # We don't advance pointer since this our packet might have been\n", " # flase detection and this flag could be the beginning of a real packet\n", " self.state = 'search'\n", " self.packet.extend(flg)\n", " packets.append(self.packet.copy())\n", " else:\n", " # packet is too short! false alarm. Keep searching \n", " # We don't advance pointer since this this flag could be the beginning of a real packet\n", " self.state = 'search'\n", " # No flag, so collect the bit and add to the packet\n", " else:\n", " # check if packet is too long... if so, must be false alarm\n", " if self.pktcounter < 2680:\n", " # Not a false alarm, collect the bit and advance pointer \n", " self.packet.append(bits[n])\n", " self.pktcounter = self.pktcounter + 1\n", " n = n + 1\n", " else: #runaway packet\n", " #runaway packet, switch state to searching, and advance pointer\n", " self.state = 'search'\n", " n = n + 1\n", " \n", " self.bitpointer = n-(len(bits)-7) \n", " return packets\n", "\n", " \n", " # function to generate a checksum for validating packets\n", " def genfcs(self,bits):\n", " # Generates a checksum from packet bits\n", " fcs = ax25.FCS()\n", " for bit in bits:\n", " fcs.update_bit(bit)\n", " \n", " digest = bitarray.bitarray(endian=\"little\")\n", " digest.frombytes(fcs.digest())\n", "\n", " return digest\n", "\n", "\n", "\n", "\n", " # function to parse packet bits to information\n", " def decodeAX25(self,bits, deepsearch=False):\n", " ax = ax25.AX25()\n", " ax.info = \"bad packet\"\n", " \n", " \n", " bitsu = ax25.bit_unstuff(bits[8:-8])\n", " \n", " \n", " foundPacket = False\n", " if (self.genfcs(bitsu[:-16]).tobytes() == bitsu[-16:].tobytes()):\n", " foundPacket = True\n", " elif deepsearch: \n", " tbits = bits[8:-8]\n", " for n in range(0,len(tbits)):\n", " tbits[n] = not tbits[n]\n", " if (self.genfcs(bitsu[:-16]).tobytes() == bitsu[-16:].tobytes()):\n", " foundPacket = True\n", " print(\"Success deep search\")\n", " break\n", " tbits[n] = not tbits[n]\n", " \n", " if foundPacket == False:\n", " return ax\n", " \n", " \n", " \n", " \n", " bytes = bitsu.tobytes()\n", " ax.destination = ax.callsign_decode(bitsu[:56])\n", " source = ax.callsign_decode(bitsu[56:112])\n", " if source[-1].isdigit() and source[-1]!=\"0\":\n", " ax.source = b\"\".join((source[:-1],'-',source[-1]))\n", " else:\n", " ax.source = source[:-1]\n", " \n", " digilen=0 \n", " \n", " if bytes[14]=='\\x03' and bytes[15]=='\\xf0':\n", " digilen = 0\n", " else:\n", " for n in range(14,len(bytes)-1):\n", " if ord(bytes[n]) & 1:\n", " digilen = (n-14)+1\n", " break\n", "\n", " # if digilen > 56:\n", " # return ax\n", " ax.digipeaters = ax.callsign_decode(bitsu[112:112+digilen*8])\n", " ax.info = bitsu[112+digilen*8+16:-16].tobytes()\n", " \n", " return ax\n", "\n", " def processBuffer(self, buff_in):\n", " \n", " # function processes an audio buffer. It collect several small into a large one\n", " # Then it demodulates and finds packets.\n", " #\n", " # The function operates as overlapp and save\n", " # The function returns packets when they become available. Otherwise, returns empty list\n", " \n", " N = self.N\n", " NN = N*3-3\n", " \n", " \n", " Nchunks = self.Nchunks\n", " Abuffer = self.Abuffer\n", " fs = self.fs\n", " Ns = self.Ns\n", " \n", " validPackets=[]\n", " packets=[]\n", " NRZI=[]\n", " idx = []\n", " bits = []\n", " \n", " # Fill in buffer at the right plave\n", " self.buff[NN+self.chunk_count*Abuffer:NN+(self.chunk_count+1)*Abuffer] = buff_in.copy()\n", " self.chunk_count = self.chunk_count + 1\n", " \n", " \n", " # number of chunk reached -- process large buffer\n", " if self.chunk_count == Nchunks:\n", " # Demodulate to get NRZI\n", " NRZI = self.demod(self.buff)\n", " # compute sampling points, using PLL\n", " idx = self.PLL(NRZI)\n", " # Sample and make a decision based on threshold\n", " bits = bitarray.bitarray((NRZI[idx]>0).tolist())\n", " # In case that buffer is too small raise an error -- must have at least 7 bits worth\n", " if len(bits) < 7:\n", " raise ValueError('number of bits too small for buffer')\n", " \n", " # concatenate end of previous buffer to current one\n", " bits = self.oldbits + self.NRZI2NRZ(bits)\n", " \n", " # store end of bit buffer to next buffer\n", " self.oldbits = bits[-7:].copy()\n", " \n", " # look for packets\n", " packets = self.findPackets(bits)\n", " \n", " # Copy end of sample buffer to the beginning of the next (overlapp and save)\n", " self.buff[:NN] = self.buff[-NN:].copy()\n", " \n", " # reset chunk counter\n", " self.chunk_count = 0\n", " \n", " # checksum test for all detected packets\n", " for n in range(0,len(packets)):\n", " if len(packets[n]) > 200: \n", " ax = self.decodeAX25(packets[n])\n", " if ax.info != 'bad packet':\n", " validPackets.append(ax)\n", " \n", " \n", " return validPackets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Testing the modem reception\n", "\n", "Now, we are ready to test our modem. Let's first load the ISS recording and see if out modem can detect the 24 packets we detected earlier. The difference is that we will load data in small buffers of 1024 samples and process them over a larger buffer made of 20-40 small buffers (corresponding to ~ 0.5 to 1 seconds)\n", "\n", "#### Task:\n", "\n", "* Load ISS.wav\n", "* Create a TNC object using `modem = TNCaprs(fs = fs,Abuffer = 1024,Nchunks = 20)`\n", "* Process the samples with the modem, 1024 samples at a time using `packets = modem.processBuffer(sig[n:n+1024])`\n", "* The method `modem.processBuffer` will return a non-empy object whenever it detects packets. Iterate on the returned objects and display the packets.\n", "* Repeat for `modem = TNCaprs(fs = fs,Abuffer = 1024,Nchunks = 1)`. You should still get 24 packets\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import urllib, ssl\n", "testfile = urllib.URLopener()\n", "testfile.context = ssl._create_unverified_context()\n", "testfile.retrieve(\"https://inst.eecs.berkeley.edu/~ee123/sp16/lab/lab5/ISS.wav\", 'ISS.wav')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fs, sig = wavread(\"ISS.wav\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "modem = TNCaprs(fs = fs,Abuffer = 1024,Nchunks = 1)\n", "\n", "npack = 0\n", "for n in range(0,len(sig),1024):\n", " packets = modem.processBuffer(sig[n:n+1024])\n", " for ax in packets: \n", " npack = npack + 1\n", " print(str(npack)+\")\",str(ax) )\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### APRS Gui Application\n", "\n", "We have reated a gui application for you. \n", "\n", "* Download aprs_app.py, ax25.py and aprs.py from the class website. \n", "* Copy and paste your TNCaprs class code into aprs.py\n", "* Run the app from the commandline (not from ipython notebooks)\n", "* Enter the appropriate audio devices and enter your callsign.\n", "* To debug that the application works, you don't need the radio. You can operate it in loopback mode\n", "* When using the radio, for best results, turn the squelch to 0 (Menue->0 then Menue again and set to 0).\n", "* Once you get the app to work, you can play with sending EMAIL, SMS (Link to howto) and text messages to other classmates. Enjoy!\n", "* You can either operate on the APRS frequency, decode packets and send in real time, try to communicate through the ISS, or use one of the digital channels and text your friends!\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sending files in loopback -- practice for the project\n", "\n", "#### Task:\n", "\n", "Write code that does the following: reads a file, breaks it into packets, modulates them and plays them on USB audio connected in loopback mode. At the same time, records the played packets, demodulates them and stores the info data into a new file. \n", "Basically, a full file transfer in loopback mode. \n", "\n", "\n", "* Open the `calBlue.tiff` file as a binary file: `f = open(\"calBlue.tiff,\"rb\")`\n", "* Create a modem using the TNCaprs with sampling rate of 11025Hz. Abuffer = 1024. Nchunks = 12\n", "* Read 256 bytes at a time, create APRS packets with the 256 bytes in the info field. \n", "* Modulate the packets and push into a Queue\n", "* Connect the interface in loopback mode\n", "* Create a play thread that will play the packets to the USB audio and a recording thread that will record from loopback USB audio.\n", "* Open a new file for writing `f_in = open(\"rec_calBlue.tiff,\"wb\")`\n", "* Write each 256 bytes you decode into the new file\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "callsign = \"KK6MRI\"\n", "fname = \"calBlue.tiff\"\n", "f = open(fname,\"rb\")\n", "\n", "fs = 11025\n", "modem = TNCaprs(fs = fs ,Abuffer = 1024,Nchunks = 12)\n", "\n", "\n", "\n", "\n", "Qin = Queue.Queue()\n", "Qout = Queue.Queue()\n", "\n", "# create a control fifo to kill threads when done\n", "cQin = Queue.Queue()\n", "cQout = Queue.Queue()\n", "\n", "# create a pyaudio object\n", "p = pyaudio.PyAudio()\n", "\n", "# initialize a recording thread. \n", "t_rec = threading.Thread(target = record_audio, args = (Qin, cQin,p, fs, dusb_in))\n", "t_play = threading.Thread(target = play_audio, args = (Qout, cQout,p, fs, dusb_out))\n", "\n", "\n", "\n", "print(\"Putting packets in Queue\")\n", "npp = 0\n", "tmp = modem.modulatPacket(callsign, \"\", \"BEGIN\", fname , preflags=80, postflags=2 ) \n", "Qout.put(tmp)\n", "while(1):\n", " bytes = f.read(256) \n", " tmp = modem.modulatPacket(callsign, \"\", str(npp), bytes, preflags=4, postflags=2 ) \n", " Qout.put(tmp)\n", " npp = npp+1\n", " if len(bytes) < 256:\n", " break\n", "tmp = modem.modulatPacket(callsign, \"\", \"END\", \"This is the end of transmission\", preflags=2, postflags=80 )\n", "Qout.put(tmp)\n", "Qout.put(\"EOT\")\n", "\n", "print(\"Done generating packets\")\n", "\n", "\n", "# start the recording and playing threads\n", "t_rec.start()\n", "time.sleep(2)\n", "t_play.start()\n", "\n", "starttime = time.time()\n", "npack = 0\n", "state = 0\n", "while(1):\n", " tmp = Qin.get()\n", " Qout.put(tmp)\n", " packets = modem.processBuffer(tmp)\n", " for ax in packets: \n", " npack = npack + 1\n", " print((str(npack)+\")\",str(ax)))\n", " if state == 0 and ax.destination[:5]==\"BEGIN\":\n", " f1 = open(\"rec_\"+ax.info,\"wb\")\n", " state = 1\n", " elif state == 1 and ax.destination[:3] == \"END\": \n", " state = 2 \n", " break\n", " elif state == 1:\n", " f1.write(ax.info)\n", " print(\"write\")\n", " if state == 2 :\n", " break\n", "\n", "print(time.time() - starttime)\n", "cQout.put(\"EOT\")\n", "cQin.put(\"EOT\")\n", "f1.close()\n", "f.close()\n", " \n", "\n", "\n", "\n", " " ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.11" } }, "nbformat": 4, "nbformat_minor": 0 }