def getAudio(self): '''Get the audio from data Returns: :obj:`commSignal`: An audio signal ''' audioFreq = self.__audioFreq strictness = self.__strictness #print(audioFreq, self.__bw) audioOut = comm.commSignal(audioFreq) bhFilter = filters.blackmanHarris(151) fmDemdulator = demod_fm.demod_fm() chunkerObj = chunker.chunker(sigsrc) #print(chunkerObj.getChunks) #print(len(chunkerObj.getChunks[:10])) for i in chunkerObj.getChunks: offset = self.__offset sig = comm.commSignal(self.__sigsrc.sampFreq, self.__sigsrc.read(*i), chunkerObj)\ .offsetFreq(self.__offset).filter(bhFilter)\ .bwLim(self.__bw, uniq="First")\ .funcApply(fmDemdulator.demod)\ .bwLim(audioFreq, strictness) audioOut.extend(sig) return audioOut
def __audio(self, audioFreq=constants.NOAA_AUDSAMPRATE, strictness=True): '''Get the audio from data at this sampling rate Args: audioFreq (:obj:`int`, optional): Target frequency of sampling of audio strictness (:obj:`bool`, optional): Strictness of sampling Returns: :obj:`commSignal`: An audio signal ''' logging.info('Beginning FM demodulation to get audio in chunks') audioOut = comm.commSignal(audioFreq) bhFilter = filters.blackmanHarris(151) fmDemdulator = demod_fm.demod_fm() chunkerObj = chunker.chunker(self.__sigsrc) for i in chunkerObj.getChunks: logging.info('Processing chunk %d of %d chunks', chunkerObj.getChunks.index(i) + 1, len(chunkerObj.getChunks)) sig = comm.commSignal( self.__sigsrc.sampFreq, self.__sigsrc.read(*i), chunkerObj).offsetFreq(self.__offset).filter(bhFilter).bwLim( self.__bw, uniq="First").funcApply( fmDemdulator.demod).bwLim(audioFreq, strictness) audioOut.extend(sig) logging.info('FM demodulation successfully complete') self.__audOut = audioOut return audioOut
sigsrc = source.IQwav(fileName) ## Next create a signal object, reading data from the source # Read all values from the source into an array sigArray = sigsrc.read(0, sigsrc.length) # a commSignal object basically stores the signal array and its samplingrate # if you want the array do sig.signal # if you want the samping rate do sig.sampRate sig = comm.commSignal(sigsrc.sampFreq, sigArray) ## Offset the frequency if required, not needed here # sig.offsetFreq(0) ########### Apply a blackman harris filter to get rid of noise bhFilter = filters.blackmanHarris(151) sig.filter(bhFilter) ## Limit bandwidth, say 30000 sig.bwLim(30000) ## FM demodulate fmDemodulator = demod_fm.demod_fm() sig.funcApply(fmDemodulator.demod) ########### APRS has two freqs 1200 and 2200, hence create a butter band pass filter from 1200-1000 to 2200+1000 bFilter = filters.butter(sig.sampRate, 1200 - 1000, 2200 + 1000, typeFlt=constants.FLT_BP) sig.filter(bFilter)
def getAccurateSync(self, useNormCorrelate=True): '''Get the sync locations: at highest sampling rate Args: useNormCorrelate (:obj:`bool`, optional): Whether to use normalized correlation or not Returns: :obj:`list`: A list of locations of sync in sample number (start of sync) ''' if self.__asyncA is None or self.__asyncB is None or self.__asyncBtime is None or self.__asyncAtime is None or self.__asyncBpk is None or self.__asyncApk is None or not self.__useNormCorrelate == useNormCorrelate: self.__useNormCorrelate = useNormCorrelate if self.__syncA is None or self.__syncB is None: self.getCrudeSync() # calculate the width of search window in sample numbers syncTime = constants.NOAA_T * len(constants.NOAA_SYNCA) searchTimeWidth = 3 * syncTime searchSampleWidth = int(searchTimeWidth * self.__sigsrc.sampFreq) # convert sync from samples to time csyncA = self.__syncA / self.__syncCrudeSampRate csyncB = self.__syncB / self.__syncCrudeSampRate # convert back to sample number csyncA *= self.__sigsrc.sampFreq csyncB *= self.__sigsrc.sampFreq ## Accurate syncA self.__asyncA = [] self.__asyncApk = [] self.__asyncAtime = [] logging.info('Beginning Accurate SyncA detection') for i in csyncA: logging.info('Detecting Sync %d of %d syncs', list(csyncA).index(i) + 1, len(csyncA)) startI = int(i) - int(searchSampleWidth) endI = int(i) + int(searchSampleWidth) if startI < 0 or endI > self.__sigsrc.length: continue sig = comm.commSignal( self.__sigsrc.sampFreq, self.__sigsrc.read( startI, endI)).offsetFreq(self.__offset).filter( filters.blackmanHarris( 151, zeroPhase=True)).funcApply( demod_fm.demod_fm().demod).funcApply( demod_am.demod_am().demod) syncDet, PkHeights, TimeSync = self.__correlateAndFindPeaks( sig, constants.NOAA_SYNCA, getExtraInfo=True, useNormCorrelate=useNormCorrelate, usePosNeedle=useNormCorrelate, useFilter=True) self.__asyncA.append(syncDet[0] + startI) self.__asyncApk.append(PkHeights[0]) self.__asyncAtime.append(TimeSync[0]) logging.info('Accurate SyncA detection complete') ## Accurate syncB self.__asyncB = [] self.__asyncBpk = [] self.__asyncBtime = [] logging.info('Beginning Accurate SyncB detection') for i in csyncB: logging.info('Detecting Sync %d of %d syncs', list(csyncB).index(i) + 1, len(csyncB)) startI = int(i) - int(searchSampleWidth) endI = int(i) + int(searchSampleWidth) if startI < 0 or endI > self.__sigsrc.length: continue sig = comm.commSignal( self.__sigsrc.sampFreq, self.__sigsrc.read( startI, endI)).offsetFreq(self.__offset).filter( filters.blackmanHarris( 151, zeroPhase=True)).funcApply( demod_fm.demod_fm().demod).funcApply( demod_am.demod_am().demod) syncDet, PkHeights, TimeSync = self.__correlateAndFindPeaks( sig, constants.NOAA_SYNCB, getExtraInfo=True, useNormCorrelate=useNormCorrelate, usePosNeedle=useNormCorrelate, useFilter=True) self.__asyncB.append(syncDet[0] + startI) self.__asyncBpk.append(PkHeights[0]) self.__asyncBtime.append(TimeSync[0]) logging.info('Accurate SyncB detection complete') return [ self.__asyncA, np.diff(self.__asyncA), self.__asyncApk, self.__asyncAtime, self.__asyncB, np.diff(self.__asyncB), self.__asyncBpk, self.__asyncBtime ]
def getMsg(self): '''Get the message from data Returns: :string: A string of message data ''' if self.__msg is None: sig = comm.commSignal(self.__sigsrc.sampFreq) chunkerObj = chunker.chunker(self.__sigsrc) bhFilter = filters.blackmanHarris(151) fmDemodObj = demod_fm.demod_fm() for i in chunkerObj.getChunks: logging.info('Processing chunk %d of %d chunks', chunkerObj.getChunks.index(i) + 1, len(chunkerObj.getChunks)) # get the signal chunkSig = comm.commSignal(self.__sigsrc.sampFreq, self.__sigsrc.read(*i), chunkerObj) ## Offset the frequency if required, not needed here chunkSig.offsetFreq(self.__offset) ## Apply a blackman harris filter to get rid of noise chunkSig.filter(bhFilter) ## Limit bandwidth chunkSig.bwLim(self.__bw) # store signal sig.extend(chunkSig) ## FM demodulate sig.funcApply(fmDemodObj.demod) logging.info('FM demod complete') ## APRS has two freqs 1200 and 2200, hence create a butter band pass filter from 1200-500 to 2200+500 sig.filter( filters.butter(sig.sampRate, 1200 - 500, 2200 + 500, typeFlt=constants.FLT_BP)) logging.info('Filtering complete') ## plot the signal if self.__graphs == 1: plt.plot(sig.signal) plt.show() buffer_size = int(np.round(self.__bw / self.__BAUDRATE)) SAMPLE_PER_BAUD = self.__bw // self.__BAUDRATE # creating the “correlation list" for the comparison frequencies of the digital frequency filers corr_mark_i = np.zeros(buffer_size) corr_mark_q = np.zeros(buffer_size) corr_space_i = np.zeros(buffer_size) corr_space_q = np.zeros(buffer_size) # filling the "correlation list" with sampled waveform for the two frequencies. for i in range(buffer_size): mark_angle = (i * 1.0 / self.__bw) / ( 1 / self.__mark_frequency) * 2 * np.pi corr_mark_i[i] = np.cos(mark_angle) corr_mark_q[i] = np.sin(mark_angle) space_angle = (i * 1.0 / self.__bw) / ( 1 / self.__space_frequency) * 2 * np.pi corr_space_i[i] = np.cos(space_angle) corr_space_q[i] = np.sin(space_angle) # now we check the full signal for the binary states, whether it is closer to 1200 hz or closer to 2200 Hz binary_filter = np.zeros(len(sig.signal)) for sample in range(len(sig.signal) - buffer_size): corr_mi = 0 corr_mq = 0 corr_si = 0 corr_sq = 0 for sub in range(buffer_size): corr_mi = corr_mi + sig.signal[sample + sub] * corr_mark_i[sub] corr_mq = corr_mq + sig.signal[sample + sub] * corr_mark_q[sub] corr_si = corr_si + sig.signal[sample + sub] * corr_space_i[sub] corr_sq = corr_sq + sig.signal[sample + sub] * corr_space_q[sub] binary_filter[sample] = (corr_mi**2 + corr_mq**2 - corr_si**2 - corr_sq**2) logging.info('Binary filter complete') if self.__graphs == 1: plt.plot(sig.signal / np.max(sig.signal)) plt.plot(np.sign(binary_filter)) plt.show() # now trying to find the raising or falling edges of the bits # generating the edge detection kernel kernel = np.zeros(SAMPLE_PER_BAUD) for i in range(len(kernel)): if i < SAMPLE_PER_BAUD // 2: kernel[i] = -1 else: kernel[i] = 1 changes = np.correlate(np.sign(binary_filter), kernel, mode="same") / SAMPLE_PER_BAUD if self.__graphs == 1: plt.plot(np.sign(binary_filter)) plt.plot(changes) plt.title("bit starts") plt.show() # by using the edges of the bits for synching the sampling to the transmitted bits, the algo is # self synchronizing. # but sometimes the crossing areas between the bits can be uncertain. for that, a peak detection defines # only one solution in close vicinity and defining the edges further. peaks = peakdetect.peakdetect(np.abs(changes), lookahead=int(SAMPLE_PER_BAUD * 0.65)) peaks1_x = [] peaks1_y = [] # positive peaks for i in range(len(peaks[0])): peaks1_x.append(peaks[0][i][0]) peaks1_y.append(peaks[0][i][1]) if self.__graphs == 1: plt.plot(peaks1_x, peaks1_y, "o") plt.plot(np.abs(changes)) plt.plot(np.sign(binary_filter)) plt.plot(sig.signal / np.max(sig.signal)) plt.show() bit_repeated = np.round( np.diff(peaks1_x) / (self.__bw / self.__BAUDRATE)) logging.info('Bit repeat complete') if self.__graphs == 1: plt.plot(np.sign(binary_filter)) plt.plot(peaks1_x[:-1], bit_repeated, "*") plt.grid() plt.title("where frequency shifts") plt.show() # making the bits for nrzi bitstream_nrzi = [] c = 0 for i in range(len(bit_repeated)): # print(c, i, x1[i], "p", int(bit_repeated[i])) for repeats in range(int(bit_repeated[i])): bitstream_nrzi.append((np.mean( binary_filter[peaks1_x[i] + repeats * SAMPLE_PER_BAUD:peaks1_x[i] + (repeats + 1) * SAMPLE_PER_BAUD]))) # print(c, bitstream_nrzi[-1]) c += 1 # here we convert the nrzi bits to normal bits bitstream = decode_afsk1200.decode_nrzi(np.sign(bitstream_nrzi)) logging.info('Decoding NRZI complete') if self.__graphs == 1: plt.plot(np.sign(bitstream_nrzi)) plt.plot(bitstream_nrzi, "o-") plt.plot(bitstream, "*") plt.show() bit_startflag = [] bit_startflag_marker = [] for bit in range(len(bitstream) - 8): out = "" for i in range(8): out += str(bitstream[bit + i]) if out == "01111110": # print(bit) bit_startflag.append(bit) bit_startflag_marker.append(1) length = np.diff(bit_startflag) # there are still the stuffed bits inside the bit stream, so we need to find them... bitstream_stuffed = decode_afsk1200.find_bit_stuffing(bitstream) if self.__graphs == 1: plt.plot(bitstream_nrzi) plt.plot(bitstream, "o-") plt.plot(bit_startflag[:-1], length, "o") plt.plot(bit_startflag, bit_startflag_marker, "o") plt.plot(bitstream_stuffed, "*") plt.title("test1") plt.show() logging.info('Stuffed bit removal complete') # checking at each possible start flag, if the bit stream was received correctly. # this is done by checking the crc16 at the end of the msg with the msg body. for flag in range(len(bit_startflag) - 1): # and firstly, we need to get rid of the stuffed bits, that are still inside the bit stream bits = decode_afsk1200.reduce_stuffed_bit( bitstream[bit_startflag[flag] + 8:bit_startflag[flag + 1]], bitstream_stuffed[bit_startflag[flag] + 8:bit_startflag[flag + 1]]) msg = bits[:-16] if len(bits) % 8 == 0 and len(msg) > 16 * 8: out = "" for i in range(len(msg)): out += str(msg[i]) crc = framechecksequence.fcs_crc16(out) crc_received = "" msg_rest = bits[-16:] for i in range(len(msg_rest)): crc_received += str(msg_rest[i]) if crc_received == crc: msg_text = decode_afsk1200.bits_to_msg(msg) print("one aprs msg with correct crc is found. #", flag, "starts at", bit_startflag[flag], "length is", len(bits) / 8) msg_text if self.__graphs == 1: plt.plot( bitstream[bit_startflag[flag] + 8:bit_startflag[flag + 1] + 8], "o-") plt.plot(bits, "*-") plt.show() # there can be several messages per stream, so for now only the last is stored. # to-do self.__msg = "template: space rocks!" self.__useful = 1 logging.info('Message extraction complete') return self.__msg