diff --git a/directdemod/decode_funcube.py b/directdemod/decode_funcube.py new file mode 100644 index 0000000..c822c5d --- /dev/null +++ b/directdemod/decode_funcube.py @@ -0,0 +1,265 @@ +''' +Funcube +''' +from directdemod import source, sink, chunker, comm, constants, filters +import numpy as np +import logging +import scipy.signal as signal +#import matplotlib.pylab as plt +import math, time + +## Inspired from: https://github.com/dbdexter-dev/meteor_demod +# Thank you! + +################# OBJECTS + +class agc(): + def __init__(self): + self.mean = 180.0 + self.dc = 0.0 + + def adjust(self, inp): + + # moving avg to get dc + self.dc = ((self.dc * ((1024*1024)-1)*1.0) + inp) / (1024*1024*1.0) + inp -= self.dc + + # moving avg of amplitude + self.mean = (self.mean * 1.0 * (65536.0 - 1) + ((np.real(inp)*np.real(inp) + np.imag(inp)*np.imag(inp))**0.5)) / 65536.0 + + # multiply the input value + if 180.0 / self.mean > 20: + return inp * 20 + else: + return inp * 180.0 / self.mean + +class costas(): + + def __init__(self): + self.freq = 0.001 + self.phase = 0.00 + self.output = np.exp(-1j*self.phase) + + self.damping = 0.70710678118 + self.bw = 0.05235833333 + self.compAlphaBeta(self.damping, self.bw) + + self.mean = 1.0 + self.plllock = False + + self.hypstore = [] + for i in range(256): + self.hypstore.append(np.tanh(i-128)) + + def compAlphaBeta(self, damping, bw): + denom = (1.0 + 2.0*damping*bw + bw*bw) + self.alpha = (4*damping*bw)/denom + self.beta = (4*bw*bw)/denom + + def loop(self, samp): + self.output = np.exp(-1j*self.phase) + + correctedIn = samp * self.output + + error = np.imag(correctedIn) * self.hyp(np.real(correctedIn))/255.0 + self.mean = (self.mean * (39999.0) + np.abs(error))/40000.0 + if error > 1: + error = 1.0 + elif (error < -1): + error = -1.0 + + self.phase = math.fmod(self.phase + self.freq + self.alpha*error, 2*np.pi) + self.freq = self.freq + self.beta*error + + if not self.plllock and self.mean < 0.2: + self.compAlphaBeta(self.damping, self.bw/2.0) + self.plllock = True + elif self.plllock and self.mean > 0.5: + self.compAlphaBeta(self.damping, self.bw) + self.plllock = False + return correctedIn + + def hyp(self, x): + if x > 127: return 1 + if x < -128: return -1 + return self.hypstore[int(x+128)] + +def lim(x): + if x < -128.0: + return -128 + if x > 127.0: + return 127 + if x > 0 and x < 1: + return 1 + if x > -1 and x < 0: + return -1 + return int(x) + +def limBin(x): + if x <= 0: + return 0 + else: + return 1 + + +''' +Object to Funcube +''' + +class decode_funcube: + + ''' + Object to decode Funcube + ''' + + def __init__(self, sigsrc, offset, bw): + + '''Initialize the object + + Args: + sigsrc (:obj:`commSignal`): IQ data source + offset (:obj:`float`): Frequency offset of source in Hz + bw (:obj:`int`, optional): Bandwidth + ''' + + self.__bw = bw + if self.__bw is None: + self.__bw = 7000 + self.__sigsrc = sigsrc + self.__offset = offset + self.__useful = 0 + + @property + def useful(self): + + '''See if signal was found + + Returns: + :obj:`int`: 0 if not found, 1 if found + ''' + + return self.__useful + + @property + def getSyncs(self): + + '''Get syncs of Funcube + + Returns: + :obj:`list`: list of detected syncs + ''' + + # create chunker object + chunkerObj = chunker.chunker(self.__sigsrc) + + # butter filter + bf = filters.butter(self.__sigsrc.sampFreq, self.__bw) + + # init vars for gardner + symbolPeriod = self.__sigsrc.sampFreq/12000 + timing = 0.00 + gardnerC, gardnerB, gardnerA = 0.00, 0.00, 0.00 + agcObj = agc() + pllObj = costas() + ctr = 0 + + sync = np.array([int(i) for i in "101000110001000000000001010111100"]) + + sync12khz = np.repeat(sync, 10) + + sync[sync == 1] = 127 + sync[sync == 0] = -128 + sync2mhz = np.repeat(sync, int(2048000/1200)) + + maxResBuff = [] + minResBuff = [] + maxBuffRetain = -1 + maxBuffStart = 0 + + minSyncs = [] + maxSyncs = [] + + numCtrs = int(chunkerObj.getChunks[-1][-1]*12000/2048000) + start_time = time.time() + lastMin = None + ctrMain = 0 + for i in chunkerObj.getChunks[:]: + + #interpolate + sig = comm.commSignal(self.__sigsrc.sampFreq, self.__sigsrc.read(*i)) + sig.offsetFreq(self.__offset) + sig.filter(bf) + + # main loop + for i in sig.signal: + + ### MAXSYNC detection by correlation + + # start storing 2mhz values near sync possible regions + if not lastMin is None and (ctr > lastMin + (4.9*12000) - (2*len(sync12khz)) or not maxBuffRetain == -1): + if len(maxResBuff) == 0: + maxBuffStart = ctrMain + maxResBuff.append(lim(np.real(i*pllObj.output)/2)) + + # see if correlation is to be performed + if maxBuffRetain == -1: + if len(maxResBuff) > (2 * len(sync2mhz)): + maxBuffStart += 1 + maxResBuff.pop(0) + elif maxBuffRetain == 0: + maxBuffRetain -= 1 + corr = np.abs(np.correlate(maxResBuff,sync2mhz, mode='same')) + logging.info("MAXSYNC %d", maxBuffStart+np.argmax(corr)) + #print("MAXSYNC", maxBuffStart, np.argmax(corr), maxBuffStart+np.argmax(corr)) + maxSyncs.append(maxBuffStart+np.argmax(corr)) + maxResBuff = [] + + #plt.plot(corr) + #plt.show() + else: + maxBuffRetain -= 1 + + # Gardners algorithm + if timing >= symbolPeriod/2 and timing < ((symbolPeriod/2)+1): + gardnerB = agcObj.adjust(i) + + elif timing >= symbolPeriod: + gardnerA = agcObj.adjust(i) + timing -= symbolPeriod + resync_error = (np.imag(gardnerA) - np.imag(gardnerC)) * np.imag(gardnerB) + timing += (resync_error*symbolPeriod/(2000000.0)) + gardnerC = gardnerA + gardnerA = pllObj.loop(gardnerA) + ctr += 1 + + # 12khz buffer + minResBuff.append(limBin(np.real(gardnerA))) + minResBuff = minResBuff[-1*len(sync12khz):] + + # print periodic status + try: + if ctr%1000 == 0: + logging.info("[%.2f percent complete] [%.2f seconds elapsed] [%.2f seconds remain]", (ctr*100/numCtrs), (time.time() - start_time), (((time.time() - start_time)/(ctr/numCtrs))-(time.time() - start_time))) + #print(ctr, '[%.2f' %(ctr*100/numCtrs),"%]",'[%.2f' %(time.time() - start_time),"seconds elapsed]",'[%.2f' %(((time.time() - start_time)/(ctr/numCtrs))-(time.time() - start_time)), "seconds remaining]", pllObj.mean) + except: + pass + + # see if sync is present + if len(minResBuff) == len(sync12khz) and np.abs(np.sum(np.abs(np.array(minResBuff) - sync12khz)) - (len(sync12khz)/2)) > 120: + logging.info("MINSYNC: %d %f",ctr, np.abs(np.sum(np.abs(np.array(minResBuff) - sync12khz)) - (len(sync12khz)/2))) + #print("MINSYNC:",ctr, np.abs(np.sum(np.abs(np.array(minResBuff) - sync12khz)) - (len(sync12khz)/2))) + minSyncs.append(ctr) + lastMin = ctr + maxBuffRetain = 2 * len(sync2mhz) + + timing += 1 + ctrMain += 1 + + if len(maxSyncs) > 0: + # check usefulness + if np.min(np.abs(np.diff(maxSyncs) - (4.98*2048000))) < (0.2*2048000): + self.__useful = 1 + return list(maxSyncs)[1:] + else: + return [] + \ No newline at end of file diff --git a/directdemod/decode_meteorm2.py b/directdemod/decode_meteorm2.py new file mode 100644 index 0000000..2160347 --- /dev/null +++ b/directdemod/decode_meteorm2.py @@ -0,0 +1,333 @@ +''' +Funcube +''' +from directdemod import source, sink, chunker, comm, constants, filters +import numpy as np +import logging +import scipy.signal as signal +#import matplotlib.pylab as plt +import math, time + +## Inspired from: https://github.com/dbdexter-dev/meteor_demod +# Thank you! + +################# OBJECTS + +class agc(): + def __init__(self): + self.mean = 3.0 + self.dc = 0.0 + + def adjust(self, inp): + + # moving avg to get dc + self.dc = ((self.dc * ((1024*1024)-1)*1.0) + inp) / (1024*1024*1.0) + inp -= self.dc + + # moving avg of amplitude + self.mean = (self.mean * 1.0 * (65536.0 - 1) + ((np.real(inp)*np.real(inp) + np.imag(inp)*np.imag(inp))**0.5)) / 65536.0 + + # multiply the input value + if 180.0 / self.mean > 200: + return inp * 200 + else: + return inp * 180.0 / self.mean + +class costas(): + + def __init__(self): + self.freq = 0.001 + self.phase = 0.00 + self.output = np.exp(-1j*self.phase) + + self.damping = 0.70710678118 + self.bw = 0.008727 + self.compAlphaBeta(self.damping, self.bw) + + self.mean = 1.0 + self.plllock = False + + self.hypstore = [] + for i in range(256): + self.hypstore.append(np.tanh(i-128)) + + def compAlphaBeta(self, damping, bw): + denom = (1.0 + 2.0*damping*bw + bw*bw) + self.alpha = (4*damping*bw)/denom + self.beta = (4*bw*bw)/denom + + def loop(self, samp): + self.output = np.exp(-1j*self.phase) + + correctedIn = samp * self.output + + error = (np.imag(correctedIn) * self.hyp(np.real(correctedIn)) - np.real(correctedIn) * self.hyp(np.imag(correctedIn)))/255.0 + + self.mean = (self.mean * (39999.0) + np.abs(error))/40000.0 + if error > 1: + error = 1.0 + elif (error < -1): + error = -1.0 + + self.phase = math.fmod(self.phase + self.freq + self.alpha*error, 2*np.pi) + self.freq = self.freq + self.beta*error + + if not self.plllock and self.mean < 0.2: + self.compAlphaBeta(self.damping, self.bw/2.0) + self.plllock = True + elif self.plllock and self.mean > 0.5: + self.compAlphaBeta(self.damping, self.bw) + self.plllock = False + return correctedIn + + def hyp(self, x): + if x > 127: return 1 + if x < -128: return -1 + return self.hypstore[int(x+128)] + +def lim(x): + if x < -128.0: + return -128 + if x > 127.0: + return 127 + if x > 0 and x < 1: + return 1 + if x > -1 and x < 0: + return -1 + return int(x) + +def limBin(x): + if x <= 0: + return 0 + else: + return 1 + + +''' +Object to Meteor m2 +''' + +class decode_meteorm2: + + ''' + Object to decode Meteor m2 + ''' + + def __init__(self, sigsrc, offset, bw): + + '''Initialize the object + + Args: + sigsrc (:obj:`commSignal`): IQ data source + offset (:obj:`float`): Frequency offset of source in Hz + bw (:obj:`int`, optional): Bandwidth + ''' + + self.__bw = bw + if self.__bw is None: + self.__bw = 70000 + self.__sigsrc = sigsrc + self.__offset = offset + self.__useful = 0 + + @property + def useful(self): + + '''See if signal was found + + Returns: + :obj:`int`: 0 if not found, 1 if found + ''' + + return self.__useful + + @property + def getSyncs(self): + + '''Get syncs of Meteor M2 + + Returns: + :obj:`list`: list of detected syncs + ''' + + # create chunker object + chunkerObj = chunker.chunker(self.__sigsrc) + + # butter filter + bf = filters.butter(self.__sigsrc.sampFreq, self.__bw) + + # init vars for gardner + symbolPeriod = self.__sigsrc.sampFreq/72000 + timing = 0.00 + gardnerC, gardnerB, gardnerA = 0.00, 0.00, 0.00 + agcObj = agc() + pllObj = costas() + ctr = 0 + + sync = [int(i) for i in "0, 13, 13, 12, 13, 13, 13, 0, 0, 0, 13, 13, 0, 13, 13, 0, 13, 0, 0, 0, 13, 13, 13, 0, 0, 13, 0, 13, 0, 13, 0, 13, 13, 0, 0, 0, 13, 13, 0, 0, 0, 0, 13, 0, 13, 13, 0, 0, 0, 0, 0, 13, 1, 13, 0, 13, 13, 13, 13, 12, 0, 13, 0, 13, 0, 0, 13, 0, 13, 0, 13, 13, 0, 13, 13, 13, 0, 0, 0, 0, 13, 0, 13, 0, 13, 13, 13, 13, 13, 0, 13, 13, 13, 0, 0, 0, 0, 13, 13, 13, 0, 13, 0, 0, 0, 13, 0, 13, 13, 0, 13, 0, 13, 13, 0, 0, 0, 13, 13, 13".split(",")] + sync = np.array(sync) + sync[sync < 7] = 0 + sync[sync >= 7] = 1 + + sync72khz = np.repeat(sync, 1) + + sync72khz1 = [] + for i in range(len(sync72khz)): + if i%2 == 0: + sync72khz1.append(sync72khz[i]) + else: + sync72khz1.append(1-sync72khz[i]) + sync72khz1 = np.array(sync72khz1) + + sync72khz2 = [] + for i in range(len(sync72khz)): + if i%2 == 1: + sync72khz2.append(sync72khz[i]) + else: + sync72khz2.append(1-sync72khz[i]) + sync72khz2 = np.array(sync72khz2) + + + sync[sync == 1] = 127 + sync[sync == 0] = -128 + sync2mhz = np.repeat(sync, int(2048000/72000)) + + sync = np.array(sync72khz1[:]) + sync[sync == 1] = 127 + sync[sync == 0] = -128 + sync2mhz1 = np.repeat(sync, int(2048000/72000)) + + sync = np.array(sync72khz2[:]) + sync[sync == 1] = 127 + sync[sync == 0] = -128 + sync2mhz2 = np.repeat(sync, int(2048000/72000)) + + maxResBuff = [] + minResBuff1 = [] + minResBuff2 = [] + maxBuffRetain = -1 + maxBuffStart = 0 + + minSyncs = [] + maxSyncs = [] + + numCtrs = int(chunkerObj.getChunks[-1][-1]*72000/2048000) + start_time = time.time() + lastMin = None + ctrMain = 0 + + sync2mhzChosen = sync2mhz + + for i in chunkerObj.getChunks[:]: + + #interpolate + sig = comm.commSignal(self.__sigsrc.sampFreq, self.__sigsrc.read(*i)) + sig.offsetFreq(self.__offset) + sig.filter(bf) + + # main loop + for i in sig.signal: + + ### MAXSYNC detection by correlation + + # start storing 2mhz values near sync possible regions + if not lastMin is None and (ctr > lastMin + (0.1*72000) - (2*len(sync72khz)) or not maxBuffRetain == -1) and not ctr > lastMin + (1*72000): + if len(maxResBuff) == 0: + maxBuffStart = ctrMain + corrVal = i*pllObj.output + maxResBuff.append(lim(np.real(corrVal)/2)) + maxResBuff.append(lim(np.imag(corrVal)/2)) + + # see if correlation is to be performed + if maxBuffRetain == -1: + if len(maxResBuff) > (2 * len(sync2mhz)): + maxBuffStart += 1 + maxResBuff.pop(0) + maxResBuff.pop(0) + elif maxBuffRetain == 0: + maxBuffRetain -= 1 + corr = np.abs(np.correlate(maxResBuff,sync2mhzChosen, mode='same')) + logging.info("MAXSYNC %d", maxBuffStart+(np.argmax(corr)/2.0)) + #print("MAXSYNC", maxBuffStart, np.argmax(corr), maxBuffStart+np.argmax(corr)) + maxSyncs.append(maxBuffStart+(np.argmax(corr)/2.0)) + maxResBuff = [] + + #plt.plot(corr) + #plt.show() + else: + maxBuffRetain -= 1 + + # Gardners algorithm + if timing >= symbolPeriod/2 and timing < ((symbolPeriod/2)+1): + gardnerB = agcObj.adjust(i) + + elif timing >= symbolPeriod: + gardnerA = agcObj.adjust(i) + timing -= symbolPeriod + resync_error = (np.imag(gardnerA) - np.imag(gardnerC)) * np.imag(gardnerB) + timing += (resync_error*symbolPeriod/(2000000.0)) + gardnerC = gardnerA + gardnerA = pllObj.loop(gardnerA) + ctr += 1 + + # print periodic status + try: + if ctr%1000 == 0: + logging.info("[%.2f percent complete] [%.2f seconds elapsed] [%.2f seconds remain]", (ctr*100/numCtrs), (time.time() - start_time), (((time.time() - start_time)/(ctr/numCtrs))-(time.time() - start_time))) + #print(ctr, '[%.2f' %(ctr*100/numCtrs),"%]",'[%.2f' %(time.time() - start_time),"seconds elapsed]",'[%.2f' %(((time.time() - start_time)/(ctr/numCtrs))-(time.time() - start_time)), "seconds remaining]", pllObj.mean) + except: + pass + + if lastMin is None or ctr > lastMin + 0.1*(72000): + # 72khz buffer + minResBuff1.append(limBin(np.real(gardnerA))) + minResBuff1.append(limBin(np.imag(gardnerA))) + minResBuff1 = minResBuff1[-1*len(sync72khz):] + + minResBuff2.append(limBin(np.imag(gardnerA))) + minResBuff2.append(limBin(np.real(gardnerA))) + minResBuff2 = minResBuff2[-1*len(sync72khz):] + + buff1corr, buff2corr, buff3corr, buff4corr, buff5corr, buff6corr = 0, 0, 0, 0, 0, 0 + if len(minResBuff1) == len(sync72khz): + buff1corr = np.abs(np.sum(np.abs(np.array(minResBuff1) - sync72khz)) - (len(sync72khz)/2)) + #if len(minResBuff2) == len(sync72khz): + # buff2corr = np.abs(np.sum(np.abs(np.array(minResBuff2) - sync72khz)) - (len(sync72khz)/2)) + + #if len(minResBuff1) == len(sync72khz1): + # buff3corr = np.abs(np.sum(np.abs(np.array(minResBuff1) - sync72khz1)) - (len(sync72khz1)/2)) + if len(minResBuff2) == len(sync72khz1): + buff4corr = np.abs(np.sum(np.abs(np.array(minResBuff2) - sync72khz1)) - (len(sync72khz1)/2)) + + #if len(minResBuff1) == len(sync72khz2): + # buff5corr = np.abs(np.sum(np.abs(np.array(minResBuff1) - sync72khz2)) - (len(sync72khz2)/2)) + #if len(minResBuff2) == len(sync72khz2): + # buff6corr = np.abs(np.sum(np.abs(np.array(minResBuff2) - sync72khz2)) - (len(sync72khz2)/2)) + + if buff1corr > 30 or buff2corr > 30: + sync2mhzChosen = sync2mhz + if buff3corr > 30 or buff4corr > 30: + sync2mhzChosen = sync2mhz1 + if buff4corr > 30 or buff6corr > 30: + sync2mhzChosen = sync2mhz2 + + # see if sync is present + if buff1corr > 30 or buff2corr > 30 or buff3corr > 30 or buff4corr > 30 or buff5corr > 30 or buff6corr > 30: + logging.info("MINSYNC: %d",ctr) + #logging.info("MINSYNC: %d %f %f %f %f %f %f",ctr, buff1corr, buff2corr, buff3corr, buff4corr, buff5corr, buff6corr) + #print("MINSYNC:",ctr, np.abs(np.sum(np.abs(np.array(minResBuff) - sync72khz)) - (len(sync72khz)/2))) + minSyncs.append(ctr) + lastMin = ctr + maxBuffRetain = 2 * len(sync2mhz) + + timing += 1 + ctrMain += 1 + + if len(maxSyncs) > 0: + # check usefulness + if np.min(np.abs(np.diff(maxSyncs) - (0.11*2048000))) < (0.05*2048000): + self.__useful = 1 + return list(maxSyncs)[1:] + else: + return [] + \ No newline at end of file diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle index 9864ad7..1f41ff7 100644 Binary files a/docs/_build/doctrees/environment.pickle and b/docs/_build/doctrees/environment.pickle differ diff --git a/docs/_build/doctrees/modules.doctree b/docs/_build/doctrees/modules.doctree index b4b3811..efa5697 100644 Binary files a/docs/_build/doctrees/modules.doctree and b/docs/_build/doctrees/modules.doctree differ diff --git a/docs/_build/html/_modules/directdemod/decode_afsk1200.html b/docs/_build/html/_modules/directdemod/decode_afsk1200.html index 3ac4113..a0df5e9 100644 --- a/docs/_build/html/_modules/directdemod/decode_afsk1200.html +++ b/docs/_build/html/_modules/directdemod/decode_afsk1200.html @@ -73,7 +73,22 @@

Source code for directdemod.decode_afsk1200

         self.__sigsrc = sigsrc
         self.__offset = offset
         self.__msg = None
-        self.__graphs = 0
+ self.__graphs = 0 + self.__useful = 0 + + @property + def useful(self): + + '''See if atleast one message was found or not + + Returns: + :obj:`int`: 0 if not found, 1 if found + ''' + + #if self.__msg is None: + # self.getMsg + + return self.__useful @property def getMsg(self): @@ -85,23 +100,39 @@

Source code for directdemod.decode_afsk1200

 
         if self.__msg is None:
 
-            # get the signal
-            sig = comm.commSignal(self.__sigsrc.sampFreq, self.__sigsrc.read(0, self.__sigsrc.length))
+            sig = comm.commSignal(self.__sigsrc.sampFreq)
 
-            ## Offset the frequency if required, not needed here
-            sig.offsetFreq(self.__offset)
+            chunkerObj = chunker.chunker(self.__sigsrc)
+            bhFilter = filters.blackmanHarris(151)
+            fmDemodObj = demod_fm.demod_fm()
+            
 
-            ## Apply a blackman harris filter to get rid of noise
-            sig.filter(filters.blackmanHarris(151))
+            for i in chunkerObj.getChunks:
 
-            ## Limit bandwidth
-            sig.bwLim(self.__bw)
+                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(demod_fm.demod_fm().demod)
+            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:
@@ -130,6 +161,7 @@ 

Source code for directdemod.decode_afsk1200

             # 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
@@ -144,7 +176,7 @@ 

Source code for directdemod.decode_afsk1200

                     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))
@@ -189,7 +221,7 @@ 

Source code for directdemod.decode_afsk1200

                 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, "*")
@@ -212,6 +244,7 @@ 

Source code for directdemod.decode_afsk1200

 
             # 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))
@@ -245,7 +278,7 @@ 

Source code for directdemod.decode_afsk1200

                 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):
@@ -269,7 +302,12 @@ 

Source code for directdemod.decode_afsk1200

                         crc_received += str(msg_rest[i])
 
                     if crc_received == crc:
-                        print("one aprs msg with correct crc is found. #", flag, "starts at", bit_startflag[flag], "length is", len(bits) / 8)
+                        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-")
@@ -279,11 +317,53 @@ 

Source code for directdemod.decode_afsk1200

                         # 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
 
+
+    def bits_to_msg(bits):
+
+        msg_text = ""
+        header_text = ""
+
+        aprs_header = 1
+
+        for byte in range(0, len(bits), 8):
+
+            tmp = bits[byte: byte + 8]
+            character = ""
+            for bit in range(len(tmp)):
+                character += str(tmp[7 - bit])
+
+            if aprs_header == 1:
+                header_text += chr(int("0" + character[:7], 2))
+
+            else:
+                msg_text += chr(int(character, 2))
+
+            if character[-1] == "1" and aprs_header == 1:
+                # header is ending here!
+                aprs_header = 0
+
+        DESTINATION_ADDRESS = header_text[:7]
+        SOURCE_ADDRESS = header_text[7:14]
+        PATH = header_text[14:]
+        print("destination:\t", DESTINATION_ADDRESS)
+        print("source:\t\t", SOURCE_ADDRESS)
+        print("path:\t\t", PATH)
+
+        CONTROL_FIELD = hex(ord(msg_text[0]))
+        PROTOCOL_ID = hex(ord(msg_text[1]))
+        INFORMATION_FIELD = msg_text[2:]
+        print("control fields:\t", CONTROL_FIELD, PROTOCOL_ID)
+        print("information:\t", INFORMATION_FIELD)
+
+        return INFORMATION_FIELD
+
+
 
[docs] def decode_nrzi(nrzi): '''Decode NRZI diff --git a/docs/_build/html/_modules/directdemod/decode_funcube.html b/docs/_build/html/_modules/directdemod/decode_funcube.html new file mode 100644 index 0000000..c9cfbd0 --- /dev/null +++ b/docs/_build/html/_modules/directdemod/decode_funcube.html @@ -0,0 +1,338 @@ + + + + + + + + directdemod.decode_funcube — DirectDemod documentation + + + + + + + + + + + + +
+
+
+
+ +

Source code for directdemod.decode_funcube

+'''
+Funcube
+'''
+from directdemod import source, sink, chunker, comm, constants, filters
+import numpy as np
+import logging
+import scipy.signal as signal
+#import matplotlib.pylab as plt
+import math, time
+
+## Inspired from: https://github.com/dbdexter-dev/meteor_demod
+# Thank you!
+
+################# OBJECTS
+
+class agc():
+    def __init__(self):
+        self.mean = 180.0
+        self.dc = 0.0
+
+    def adjust(self, inp):
+
+        # moving avg to get dc
+        self.dc = ((self.dc * ((1024*1024)-1)*1.0) + inp) / (1024*1024*1.0)
+        inp -= self.dc
+
+        # moving avg of amplitude
+        self.mean = (self.mean * 1.0 * (65536.0 - 1) + ((np.real(inp)*np.real(inp) + np.imag(inp)*np.imag(inp))**0.5)) / 65536.0
+
+        # multiply the input value
+        if 180.0 / self.mean > 20:
+            return inp * 20
+        else:
+            return inp * 180.0 / self.mean
+
+class costas():
+
+    def __init__(self):
+        self.freq = 0.001
+        self.phase = 0.00
+        self.output = np.exp(-1j*self.phase)
+
+        self.damping = 0.70710678118
+        self.bw = 0.05235833333
+        self.compAlphaBeta(self.damping, self.bw)
+
+        self.mean = 1.0
+        self.plllock = False
+
+        self.hypstore = []
+        for i in range(256):
+            self.hypstore.append(np.tanh(i-128))
+
+    def compAlphaBeta(self, damping, bw):
+        denom = (1.0 + 2.0*damping*bw + bw*bw)
+        self.alpha = (4*damping*bw)/denom
+        self.beta = (4*bw*bw)/denom
+
+    def loop(self, samp):
+        self.output = np.exp(-1j*self.phase)
+
+        correctedIn = samp * self.output
+
+        error = np.imag(correctedIn) * self.hyp(np.real(correctedIn))/255.0
+        self.mean = (self.mean * (39999.0) + np.abs(error))/40000.0
+        if error > 1: 
+            error = 1.0
+        elif (error < -1):
+            error = -1.0
+
+        self.phase = math.fmod(self.phase + self.freq + self.alpha*error, 2*np.pi)
+        self.freq = self.freq + self.beta*error
+
+        if not self.plllock and self.mean < 0.2:
+            self.compAlphaBeta(self.damping, self.bw/2.0)
+            self.plllock = True
+        elif self.plllock and self.mean > 0.5:
+            self.compAlphaBeta(self.damping, self.bw)
+            self.plllock = False
+        return correctedIn
+
+    def hyp(self, x):
+        if x > 127: return 1
+        if x < -128: return -1
+        return self.hypstore[int(x+128)]
+
+def lim(x):
+    if x < -128.0:
+        return -128
+    if x > 127.0:
+        return 127
+    if x > 0 and x < 1:
+        return 1
+    if x > -1 and x < 0:
+        return -1
+    return int(x)
+
+def limBin(x):
+    if x <= 0:
+        return 0
+    else:
+        return 1
+
+
+'''
+Object to Funcube
+'''
+
+
[docs]class decode_funcube: + + ''' + Object to decode Funcube + ''' + +
[docs] def __init__(self, sigsrc, offset, bw): + + '''Initialize the object + + Args: + sigsrc (:obj:`commSignal`): IQ data source + offset (:obj:`float`): Frequency offset of source in Hz + bw (:obj:`int`, optional): Bandwidth + ''' + + self.__bw = bw + if self.__bw is None: + self.__bw = 7000 + self.__sigsrc = sigsrc + self.__offset = offset + self.__useful = 0
+ + @property + def useful(self): + + '''See if signal was found + + Returns: + :obj:`int`: 0 if not found, 1 if found + ''' + + return self.__useful + + @property + def getSyncs(self): + + '''Get syncs of Funcube + + Returns: + :obj:`list`: list of detected syncs + ''' + + # create chunker object + chunkerObj = chunker.chunker(self.__sigsrc) + + # butter filter + bf = filters.butter(self.__sigsrc.sampFreq, self.__bw) + + # init vars for gardner + symbolPeriod = self.__sigsrc.sampFreq/12000 + timing = 0.00 + gardnerC, gardnerB, gardnerA = 0.00, 0.00, 0.00 + agcObj = agc() + pllObj = costas() + ctr = 0 + + sync = np.array([int(i) for i in "101000110001000000000001010111100"]) + + sync12khz = np.repeat(sync, 10) + + sync[sync == 1] = 127 + sync[sync == 0] = -128 + sync2mhz = np.repeat(sync, int(2048000/1200)) + + maxResBuff = [] + minResBuff = [] + maxBuffRetain = -1 + maxBuffStart = 0 + + minSyncs = [] + maxSyncs = [] + + numCtrs = int(chunkerObj.getChunks[-1][-1]*12000/2048000) + start_time = time.time() + lastMin = 0 + ctrMain = 0 + for i in chunkerObj.getChunks[:]: + + #interpolate + sig = comm.commSignal(self.__sigsrc.sampFreq, self.__sigsrc.read(*i)) + sig.offsetFreq(self.__offset) + sig.filter(bf) + + # main loop + for i in sig.signal: + + ### MAXSYNC detection by correlation + + # start storing 2mhz values near sync possible regions + if ctr > lastMin + (4.9*12000) - (2*len(sync12khz)) or not maxBuffRetain == -1: + if len(maxResBuff) == 0: + maxBuffStart = ctrMain + maxResBuff.append(lim(np.real(i*pllObj.output)/2)) + + # see if correlation is to be performed + if maxBuffRetain == -1: + if len(maxResBuff) > (2 * len(sync2mhz)): + maxBuffStart += 1 + maxResBuff.pop(0) + elif maxBuffRetain == 0: + maxBuffRetain -= 1 + corr = np.abs(np.correlate(maxResBuff,sync2mhz, mode='same')) + logging.info("MAXSYNC %d", maxBuffStart+np.argmax(corr)) + #print("MAXSYNC", maxBuffStart, np.argmax(corr), maxBuffStart+np.argmax(corr)) + maxSyncs.append(maxBuffStart+np.argmax(corr)) + maxResBuff = [] + + #plt.plot(corr) + #plt.show() + else: + maxBuffRetain -= 1 + + # Gardners algorithm + if timing >= symbolPeriod/2 and timing < ((symbolPeriod/2)+1): + gardnerB = agcObj.adjust(i) + + elif timing >= symbolPeriod: + gardnerA = agcObj.adjust(i) + timing -= symbolPeriod + resync_error = (np.imag(gardnerA) - np.imag(gardnerC)) * np.imag(gardnerB) + timing += (resync_error*symbolPeriod/(2000000.0)) + gardnerC = gardnerA + gardnerA = pllObj.loop(gardnerA) + ctr += 1 + + # 12khz buffer + minResBuff.append(limBin(np.real(gardnerA))) + minResBuff = minResBuff[-1*len(sync12khz):] + + # print periodic status + try: + if ctr%1000 == 0: + logging.info("[%.2f percent complete] [%.2f seconds elapsed] [%.2f seconds remain]", (ctr*100/numCtrs), (time.time() - start_time), (((time.time() - start_time)/(ctr/numCtrs))-(time.time() - start_time))) + #print(ctr, '[%.2f' %(ctr*100/numCtrs),"%]",'[%.2f' %(time.time() - start_time),"seconds elapsed]",'[%.2f' %(((time.time() - start_time)/(ctr/numCtrs))-(time.time() - start_time)), "seconds remaining]", pllObj.mean) + except: + pass + + # see if sync is present + if len(minResBuff) == len(sync12khz) and np.abs(np.sum(np.abs(np.array(minResBuff) - sync12khz)) - (len(sync12khz)/2)) > 120: + logging.info("MINSYNC: %d %f",ctr, np.abs(np.sum(np.abs(np.array(minResBuff) - sync12khz)) - (len(sync12khz)/2))) + #print("MINSYNC:",ctr, np.abs(np.sum(np.abs(np.array(minResBuff) - sync12khz)) - (len(sync12khz)/2))) + minSyncs.append(ctr) + lastMin = ctr + maxBuffRetain = 2 * len(sync2mhz) + + timing += 1 + ctrMain += 1 + + # check usefulness + if np.min(np.abs(np.diff(maxSyncs) - (4.98*2048000))) < (0.2*2048000): + self.__useful = 1 + + return list(maxSyncs)[1:]
+ +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/directdemod/decode_meteorm2.html b/docs/_build/html/_modules/directdemod/decode_meteorm2.html new file mode 100644 index 0000000..9d02ec9 --- /dev/null +++ b/docs/_build/html/_modules/directdemod/decode_meteorm2.html @@ -0,0 +1,360 @@ + + + + + + + + directdemod.decode_meteorm2 — DirectDemod documentation + + + + + + + + + + + + +
+
+
+
+ +

Source code for directdemod.decode_meteorm2

+'''
+Funcube
+'''
+from directdemod import source, sink, chunker, comm, constants, filters
+import numpy as np
+import logging
+import scipy.signal as signal
+#import matplotlib.pylab as plt
+import math, time
+
+## Inspired from: https://github.com/dbdexter-dev/meteor_demod
+# Thank you!
+
+################# OBJECTS
+
+class agc():
+    def __init__(self):
+        self.mean = 180.0
+        self.dc = 0.0
+
+    def adjust(self, inp):
+
+        # moving avg to get dc
+        self.dc = ((self.dc * ((1024*1024)-1)*1.0) + inp) / (1024*1024*1.0)
+        inp -= self.dc
+
+        # moving avg of amplitude
+        self.mean = (self.mean * 1.0 * (65536.0 - 1) + ((np.real(inp)*np.real(inp) + np.imag(inp)*np.imag(inp))**0.5)) / 65536.0
+
+        # multiply the input value
+        if 180.0 / self.mean > 20:
+            return inp * 20
+        else:
+            return inp * 180.0 / self.mean
+
+class costas():
+
+    def __init__(self):
+        self.freq = 0.001
+        self.phase = 0.00
+        self.output = np.exp(-1j*self.phase)
+
+        self.damping = 0.70710678118
+        self.bw = 0.008727
+        self.compAlphaBeta(self.damping, self.bw)
+
+        self.mean = 1.0
+        self.plllock = False
+
+        self.hypstore = []
+        for i in range(256):
+            self.hypstore.append(np.tanh(i-128))
+
+    def compAlphaBeta(self, damping, bw):
+        denom = (1.0 + 2.0*damping*bw + bw*bw)
+        self.alpha = (4*damping*bw)/denom
+        self.beta = (4*bw*bw)/denom
+
+    def loop(self, samp):
+        self.output = np.exp(-1j*self.phase)
+
+        correctedIn = samp * self.output
+
+        error = (np.imag(correctedIn) * self.hyp(np.real(correctedIn)) - np.real(correctedIn) * self.hyp(np.imag(correctedIn)))/255.0
+
+        self.mean = (self.mean * (39999.0) + np.abs(error))/40000.0
+        if error > 1: 
+            error = 1.0
+        elif (error < -1):
+            error = -1.0
+
+        self.phase = math.fmod(self.phase + self.freq + self.alpha*error, 2*np.pi)
+        self.freq = self.freq + self.beta*error
+
+        if not self.plllock and self.mean < 0.2:
+            self.compAlphaBeta(self.damping, self.bw/2.0)
+            self.plllock = True
+        elif self.plllock and self.mean > 0.5:
+            self.compAlphaBeta(self.damping, self.bw)
+            self.plllock = False
+        return correctedIn
+
+    def hyp(self, x):
+        if x > 127: return 1
+        if x < -128: return -1
+        return self.hypstore[int(x+128)]
+
+def lim(x):
+    if x < -128.0:
+        return -128
+    if x > 127.0:
+        return 127
+    if x > 0 and x < 1:
+        return 1
+    if x > -1 and x < 0:
+        return -1
+    return int(x)
+
+def limBin(x):
+    if x <= 0:
+        return 0
+    else:
+        return 1
+
+
+'''
+Object to Meteor m2
+'''
+
+
[docs]class decode_meteorm2: + + ''' + Object to decode Meteor m2 + ''' + +
[docs] def __init__(self, sigsrc, offset, bw): + + '''Initialize the object + + Args: + sigsrc (:obj:`commSignal`): IQ data source + offset (:obj:`float`): Frequency offset of source in Hz + bw (:obj:`int`, optional): Bandwidth + ''' + + self.__bw = bw + if self.__bw is None: + self.__bw = 70000 + self.__sigsrc = sigsrc + self.__offset = offset + self.__useful = 0
+ + @property + def useful(self): + + '''See if signal was found + + Returns: + :obj:`int`: 0 if not found, 1 if found + ''' + + return self.__useful + + @property + def getSyncs(self): + + '''Get syncs of Funcube + + Returns: + :obj:`list`: list of detected syncs + ''' + + # create chunker object + chunkerObj = chunker.chunker(self.__sigsrc) + + # butter filter + bf = filters.butter(self.__sigsrc.sampFreq, self.__bw) + + # init vars for gardner + symbolPeriod = self.__sigsrc.sampFreq/72000 + timing = 0.00 + gardnerC, gardnerB, gardnerA = 0.00, 0.00, 0.00 + agcObj = agc() + pllObj = costas() + ctr = 0 + + sync = [int(i) for i in "0, 13, 13, 12, 13, 13, 13, 0, 0, 0, 13, 13, 0, 13, 13, 0, 13, 0, 0, 0, 13, 13, 13, 0, 0, 13, 0, 13, 0, 13, 0, 13, 13, 0, 0, 0, 13, 13, 0, 0, 0, 0, 13, 0, 13, 13, 0, 0, 0, 0, 0, 13, 1, 13, 0, 13, 13, 13, 13, 12, 0, 13, 0, 13, 0, 0, 13, 0, 13, 0, 13, 13, 0, 13, 13, 13, 0, 0, 0, 0, 13, 0, 13, 0, 13, 13, 13, 13, 13, 0, 13, 13, 13, 0, 0, 0, 0, 13, 13, 13, 0, 13, 0, 0, 0, 13, 0, 13, 13, 0, 13, 0, 13, 13, 0, 0, 0, 13, 13, 13".split(",")] + sync = np.array(sync) + sync[sync < 7] = 0 + sync[sync >= 7] = 1 + + sync72khz = np.repeat(sync, 1) + + sync[sync == 1] = 127 + sync[sync == 0] = -128 + sync2mhz = np.repeat(sync, int(2048000/72000)) + + maxResBuff = [] + minResBuff1 = [] + minResBuff2 = [] + maxBuffRetain = -1 + maxBuffStart = 0 + + minSyncs = [] + maxSyncs = [] + + numCtrs = int(chunkerObj.getChunks[-1][-1]*72000/2048000) + start_time = time.time() + lastMin = None + ctrMain = 0 + + for i in chunkerObj.getChunks[:]: + + #interpolate + sig = comm.commSignal(self.__sigsrc.sampFreq, self.__sigsrc.read(*i)+ (127.5 + 1j*127.5)) + sig.offsetFreq(self.__offset) + sig.filter(bf) + + # main loop + for i in sig.signal: + + ### MAXSYNC detection by correlation + + # start storing 2mhz values near sync possible regions + if not lastMin is None and (ctr > lastMin + (0.1*72000) - (2*len(sync72khz)) or not maxBuffRetain == -1): + if len(maxResBuff) == 0: + maxBuffStart = ctrMain + corrVal = i*pllObj.output + maxResBuff.append(lim(np.real(corrVal)/2)) + maxResBuff.append(lim(np.imag(corrVal)/2)) + + # see if correlation is to be performed + if maxBuffRetain == -1: + if len(maxResBuff) > (2 * len(sync2mhz)): + maxBuffStart += 1 + maxResBuff.pop(0) + maxResBuff.pop(0) + elif maxBuffRetain == 0: + maxBuffRetain -= 1 + corr = np.abs(np.correlate(maxResBuff,sync2mhz, mode='same')) + logging.info("MAXSYNC %d", maxBuffStart+(np.argmax(corr)/2.0)) + #print("MAXSYNC", maxBuffStart, np.argmax(corr), maxBuffStart+np.argmax(corr)) + maxSyncs.append(maxBuffStart+(np.argmax(corr)/2.0)) + maxResBuff = [] + + #plt.plot(corr) + #plt.show() + else: + maxBuffRetain -= 1 + + # Gardners algorithm + if timing >= symbolPeriod/2 and timing < ((symbolPeriod/2)+1): + gardnerB = agcObj.adjust(i) + + elif timing >= symbolPeriod: + gardnerA = agcObj.adjust(i) + timing -= symbolPeriod + resync_error = (np.imag(gardnerA) - np.imag(gardnerC)) * np.imag(gardnerB) + timing += (resync_error*symbolPeriod/(2000000.0)) + gardnerC = gardnerA + gardnerA = pllObj.loop(gardnerA) + ctr += 1 + + # 72khz buffer + minResBuff1.append(limBin(np.real(gardnerA))) + minResBuff1.append(limBin(np.imag(gardnerA))) + minResBuff1 = minResBuff1[-1*len(sync72khz):] + + minResBuff2.append(limBin(np.imag(gardnerA))) + minResBuff2.append(limBin(np.real(gardnerA))) + minResBuff2 = minResBuff2[-1*len(sync72khz):] + + # print periodic status + try: + if ctr%1000 == 0: + logging.info("[%.2f percent complete] [%.2f seconds elapsed] [%.2f seconds remain]", (ctr*100/numCtrs), (time.time() - start_time), (((time.time() - start_time)/(ctr/numCtrs))-(time.time() - start_time))) + #print(ctr, '[%.2f' %(ctr*100/numCtrs),"%]",'[%.2f' %(time.time() - start_time),"seconds elapsed]",'[%.2f' %(((time.time() - start_time)/(ctr/numCtrs))-(time.time() - start_time)), "seconds remaining]", pllObj.mean) + except: + pass + + buff1corr, buff2corr = 0, 0 + if len(minResBuff1) == len(sync72khz): + buff1corr = np.abs(np.sum(np.abs(np.array(minResBuff1) - sync72khz)) - (len(sync72khz)/2)) + if len(minResBuff2) == len(sync72khz): + buff2corr = np.abs(np.sum(np.abs(np.array(minResBuff2) - sync72khz)) - (len(sync72khz)/2)) + + # see if sync is present + if buff1corr > 30 or buff2corr > 30: + logging.info("MINSYNC: %d %f %f",ctr, buff1corr, buff2corr) + #print("MINSYNC:",ctr, np.abs(np.sum(np.abs(np.array(minResBuff) - sync72khz)) - (len(sync72khz)/2))) + minSyncs.append(ctr) + lastMin = ctr + maxBuffRetain = 2 * len(sync2mhz) + + timing += 1 + ctrMain += 1 + + if len(maxSyncs) > 0: + # check usefulness + if np.min(np.abs(np.diff(maxSyncs) - (0.11*2048000))) < (0.05*2048000): + self.__useful = 1 + return list(maxSyncs)[1:] + else: + return []
+ +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/directdemod/decode_noaa.html b/docs/_build/html/_modules/directdemod/decode_noaa.html index bd12176..066d9e1 100644 --- a/docs/_build/html/_modules/directdemod/decode_noaa.html +++ b/docs/_build/html/_modules/directdemod/decode_noaa.html @@ -42,6 +42,12 @@

Source code for directdemod.decode_noaa

 import logging, colorsys
 import scipy.signal as signal
 from scipy import stats
+import scipy.ndimage
+from datetime import datetime, timedelta
+import matplotlib.pyplot as plt
+from scipy import ndimage
+from scipy import misc
+from PIL import Image
 
 '''
 Object to decode NOAA APT
@@ -79,7 +85,37 @@ 

Source code for directdemod.decode_noaa

         self.__asyncBpk = None
         self.__asyncBtime = None
         self.__useNormCorrelate = None
-        self.__color = None
+ self.__color = None + self.__useful = 0 + self.__chIDA = None + self.__chIDB = None
+ + @property + def channelID(self): + '''get channel ID's + + Returns: + :obj:`list`: [channelIDA, channelIDB] + ''' + + if self.__image is None: + self.getImage + + return [self.__chIDA, self.__chIDB] + + @property + def useful(self): + + '''See if some data was found or not: 10 consecutive syncs apart by 0.5s+-error + + Returns: + :obj:`int`: 0 if not found, 1 if found + ''' + + if self.__syncA is None or self.__syncB is None: + self.getCrudeSync() + + return self.__useful @property def getAudio(self): @@ -95,6 +131,163 @@

Source code for directdemod.decode_noaa

 
         return self.__extractedAudio
 
+
[docs] def getMapImage(self, cTime, destFileRot, destFileNoRot, satellite, tleFile = None): + + '''Get the map overlay of the image + + Args: + cTime (:obj:`datetime`): Time of start of capture in UTC + tleFile (:obj:`str`, optional): TLE file location, pulls latest from internet if not given + destFile (:obj:`str`): location where to store the image + satellite (:obj:`str`): Satellite name, ex: NOAA 19 etc. + + ''' + + try: + from pyorbital.orbital import Orbital + from pyorbital import tlefile + except ImportError: + logging.error('pyorbital not installed') + return + + basemapPresent = False + cartopyPresent = False + + try: + from mpl_toolkits.basemap import Basemap + basemapPresent = True + except ImportError: + logging.warning('basemap not installed') + + if not basemapPresent: + try: + import cartopy.crs as ccrs + import cartopy.feature + cartopyPresent = True + except ImportError: + logging.error('Both basemap and cartopy not installed. Please install either.') + return + + def angleFromCoordinate(lat1, long1, lat2, long2): + # source: https://stackoverflow.com/questions/3932502/calculate-angle-between-two-latitude-longitude-points + lat1 = np.radians(lat1) + long1 = np.radians(long1) + lat2 = np.radians(lat2) + long2 = np.radians(long2) + + dLon = (long2 - long1) + + y = np.sin(dLon) * np.cos(lat2) + x = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos(lat2) * np.cos(dLon) + brng = np.arctan2(y, x) + brng = np.degrees(brng) + brng = (brng + 360) % 360 + brng = 360 - brng + return brng + + if tleFile is None: + orb = Orbital(satellite) + else: + orb = Orbital(satellite, tle_file=tleFile) + + im = self.getImageA + im = im[:,85:995] + oim = im[:] + + tdelta = int(im.shape[0]/16) + if tdelta < 10: + tdelta = 10 + + top = orb.get_lonlatalt(cTime + timedelta(seconds=int(im.shape[0]/4) - tdelta))[:2][::-1] + bot = orb.get_lonlatalt(cTime + timedelta(seconds=int(im.shape[0]/4) + tdelta))[:2][::-1] + center = orb.get_lonlatalt(cTime + timedelta(seconds=int(im.shape[0]/4)))[:2][::-1] + + rot = angleFromCoordinate(*bot, *top) + + if basemapPresent: + rotated_img = ndimage.rotate(im, rot) + rimg = rotated_img[:] + w = rotated_img.shape[1] + h = rotated_img.shape[0] + + m = Basemap(projection='cass',lon_0 = center[1],lat_0 = center[0],width = w*4000*0.81,height = h*4000*0.81, resolution = "i") + m.drawcoastlines(color='yellow') + m.drawcountries(color='yellow') + + im = plt.imshow(rotated_img, cmap='gray', extent=(*plt.xlim(), *plt.ylim())) + + plt.savefig(destFileRot, bbox_inches='tight', dpi=1000) + + img = misc.imread(destFileRot) + img = img[109:-109,109:-109,:] + img = misc.imresize(img, rimg.shape) + if 90 < (rot%360) < 270: + img = ndimage.rotate(img, -1 * (rot%180)) + else: + img = ndimage.rotate(img, -1 * rot) + + rf = int((img.shape[0]/2) - oim.shape[0]/2) + re = int((img.shape[0]/2) + oim.shape[0]/2) + cf = int((img.shape[1]/2) - oim.shape[1]/2) + ce = int((img.shape[1]/2) + oim.shape[1]/2) + img = img[rf:re,cf:ce] + + img = Image.fromarray(img) + + try: + img.save(destFileNoRot) + except: + logging.error('Image reverse rotation failed') + + elif cartopyPresent: + + def add_m(center, dx, dy): + # source: https://stackoverflow.com/questions/7477003/calculating-new-longitude-latitude-from-old-n-meters + new_latitude = center[0] + (dy / 6371000.0) * (180 / np.pi) + new_longitude = center[1] + (dx / 6371000.0) * (180 / np.pi) / np.cos(center[0] * np.pi/180) + return [new_latitude, new_longitude] + + fig = plt.figure() + + img = ndimage.rotate(im, rot) + rimg = img[:] + + dx = img.shape[0]*4000/2*0.81 # in meters + dy = img.shape[1]*4000/2*0.81 # in meters + + leftbot = add_m(center, -1*dx, -1*dy) + righttop = add_m(center, dx, dy) + + img_extent = (leftbot[1], righttop[1], leftbot[0], righttop[0]) + + ax = plt.axes(projection=ccrs.PlateCarree()) + ax.imshow(img, origin='upper', cmap='gray', extent=img_extent, transform=ccrs.PlateCarree()) + ax.coastlines(resolution='50m', color='yellow', linewidth=1) + ax.add_feature(cartopy.feature.BORDERS, linestyle='-', edgecolor='yellow') + + plt.savefig(destFileRot, bbox_inches='tight', dpi=1000) + + img = misc.imread(destFileRot) + img = img[109:-109,109:-109,:] + img = misc.imresize(img, rimg.shape) + if 90 < (rot%360) < 270: + img = ndimage.rotate(img, -1 * (rot%180)) + else: + img = ndimage.rotate(img, -1 * rot) + + rf = int((img.shape[0]/2) - oim.shape[0]/2) + re = int((img.shape[0]/2) + oim.shape[0]/2) + cf = int((img.shape[1]/2) - oim.shape[1]/2) + ce = int((img.shape[1]/2) + oim.shape[1]/2) + img = img[rf:re,cf:ce] + + img = Image.fromarray(img) + try: + img.save(destFileNoRot) + except: + logging.error('Image reverse rotation failed')
+ + @property def getImage(self): @@ -120,48 +313,30 @@

Source code for directdemod.decode_noaa

             amSig = self.__getAM(amSig)
 
             # convert sync from samples to time
-            csync = self.__syncA / self.__syncCrudeSampRate
+            csyncA = self.__syncA / self.__syncCrudeSampRate
+            csyncB = self.__syncB / self.__syncCrudeSampRate
 
             # convert back to sample number
-            csync *= amSig.sampRate
+            csyncA *= amSig.sampRate
+            csyncB *= amSig.sampRate
 
             # store uncorrected csync
-            ucsync = csync[:]
+            ucsync = csyncA[:]
 
             # correct any missing syncs
+            csyncA = self.__fillSync(csyncA, amSig.length)
+            csyncB = self.__fillSync(csyncB, amSig.length)
 
-            syncDIff = np.diff(csync)
-            modeSyncDIff = max(set(syncDIff), key=list(syncDIff).count)
-            wiggleRoom = 100
-
-            validSyncs = []
-            for i in range(len(csync) - 1):
-                if abs(csync[i+1] - csync[i] - modeSyncDIff) < wiggleRoom:
-                    if csync[i] not in validSyncs:
-                        validSyncs.append(csync[i])
-                    if csync[i+1] not in validSyncs:
-                        validSyncs.append(csync[i+1])
-
-            correctedSyncs = validSyncs
-
-            # initial correction
-            c = validSyncs[0] - modeSyncDIff
-            while(c > wiggleRoom):
-                correctedSyncs.append(c)
-                c -= modeSyncDIff
-
-            # later corrections
-            anchor = 0
-            c = modeSyncDIff
-            while(validSyncs[anchor] + c < amSig.length):
-                if (anchor + 1) < len(validSyncs) and abs(validSyncs[anchor + 1] - c - validSyncs[anchor]) < wiggleRoom:
-                    anchor += 1
-                    c = modeSyncDIff
-                else:
-                    correctedSyncs.append(validSyncs[anchor] + c)
-                    c += modeSyncDIff
+            # we want channel A first always
+            if csyncB[0] < csyncA[0]:
+                csyncB.pop(0)
+
+            if csyncB[-1] < csyncA[-1]:
+                csyncA.pop(-1)
 
-            csync = list(np.sort(correctedSyncs))
+            if not len(csyncA) == len(csyncB):
+                logging.error("Number of syncA and syncB unequal")
+                csyncB = np.array(csyncA) +  int(0.25 * amSig.sampRate)
 
             self.__image = []
             imageBuffer = []
@@ -177,6 +352,7 @@ 

Source code for directdemod.decode_noaa

             lowFifo, highFifo = [], []
             corrfifo = []
             corrfifosig = []
+            corrfifosig2 = []
             ncorrfifo = 3
             lcorr = None
             lcorrsig = None
@@ -185,28 +361,41 @@ 

Source code for directdemod.decode_noaa

             valuesSigCorr = []
             self.__slope = None
             self.__intercept = None
+            chidFifo1 = []
+            chidFifo2 = []
 
-            for i in csync:
+            for syncIndex in range(len(csyncA)):
 
-                logging.info('Decoding line %d of %d lines', list(csync).index(i) + 1, len(csync))
+                logging.info('Decoding line %d of %d lines', syncIndex + 1, len(csyncA))
 
-                startI = int(i)
-                endI = int(i) + int(0.5 * amSig.sampRate)
+                startIA = int(csyncA[syncIndex])
+                startIB = int(csyncB[syncIndex])
 
-                if endI > amSig.length:
+                endIA = startIB
+                endIB = startIB + int(0.25 * amSig.sampRate)
+                if 1+syncIndex < len(csyncA):
+                    endIB = int(csyncA[syncIndex + 1])
+                    
+                if endIB > amSig.length or endIA > amSig.length or startIA < 0 or startIB < 0:
                     continue
 
-                imgLine = amSig.signal[startI:endI]
-                imgLine = signal.resample(imgLine, int(len(imgLine)/numPixels) * numPixels)
-                imgLine = np.reshape(imgLine, (numPixels, int(len(imgLine)/numPixels)))
+                imgLineA = amSig.signal[startIA:endIA]
+                imgLineB = amSig.signal[startIB:endIB]
+
+
+                imgLineA = signal.resample(imgLineA, int(int(len(imgLineA)/(numPixels*0.5)) * (numPixels*0.5)))
+                imgLineB = signal.resample(imgLineB, int(int(len(imgLineB)/(numPixels*0.5)) * (numPixels*0.5)))
+
+                imgLineA = np.reshape(imgLineA, (int(numPixels*0.5), int(len(imgLineA)/(numPixels*0.5))))
+                imgLineB = np.reshape(imgLineB, (int(numPixels*0.5), int(len(imgLineB)/(numPixels*0.5))))
 
                 # image color correction based on sync
-                if i in ucsync:
+                if csyncA[syncIndex] in ucsync:
                     for j in range(len(constants.NOAA_SYNCA)):
                         if constants.NOAA_SYNCA[j] == 0:
-                            lowFifo.extend(imgLine[j])
+                            lowFifo.extend(imgLineA[j])
                         else:
-                            highFifo.extend(imgLine[j])
+                            highFifo.extend(imgLineA[j])
                         lowFifo = lowFifo[-1*constants.NOAA_COLORCORRECT_FIFOLEN:]
                         highFifo = highFifo[-1*constants.NOAA_COLORCORRECT_FIFOLEN:]
                     val11, val244 = np.median(lowFifo), np.median(highFifo)
@@ -217,7 +406,7 @@ 

Source code for directdemod.decode_noaa

 
                 # image color correction based on calibration strip
                 lengthOfStrip = int((len(constants.NOAA_SYNCA) * constants.NOAA_T) * amSig.sampRate)
-                stripVal = np.median(amSig.signal[startI - lengthOfStrip:startI])
+                stripVal = np.median(amSig.signal[startIA - lengthOfStrip:startIA])
 
                 corrfifo.append(255 * (stripVal - self.__low) / (self.__high - self.__low))
                 corrfifo = corrfifo[-1*ncorrfifo:]
@@ -227,9 +416,22 @@ 

Source code for directdemod.decode_noaa

                 corrfifosig = corrfifosig[-1*ncorrfifo:]
                 outcorrsig = np.median(corrfifosig)
 
+                lengthOfStrip2 = int((len(constants.NOAA_SYNCB) * constants.NOAA_T) * amSig.sampRate)
+                stripVal2 = np.median(amSig.signal[startIB - lengthOfStrip2:startIB])
+
+                corrfifosig2.append(stripVal2)
+                corrfifosig2 = corrfifosig2[-1*ncorrfifo:]
+                outcorrsig2 = np.median(corrfifosig2)
+
+                chidFifo1.append(outcorrsig2)
+                chidFifo1 = chidFifo1[-100:]
+
+                chidFifo2.append(outcorrsig)
+                chidFifo2 = chidFifo2[-100:]
+
                 if lcorr is None or abs(outcorr - lcorr) > 255.0/16:
                     logging.info('Color correction state: %d', statecorr)
-                    if statecorr == 0:
+                    if statecorr == 0 and not lcorrsig is None:
                         valuesPixCorr = [lcorr, outcorr]
                         valuesSigCorr = [lcorrsig, outcorrsig]
                         statecorr = 1
@@ -246,6 +448,12 @@ 

Source code for directdemod.decode_noaa

                             valuesSigCorr = [outcorrsig] + valuesSigCorr
                             self.__slope, self.__intercept, r_value, p_value, std_err = stats.linregress(valuesSigCorr,np.array([i for i in range(9)]) * 255.0/8)
                             logging.info('Color correction bingo slope: %f intercept: %f', self.__slope, self.__intercept)
+                            if len(chidFifo1) > 1+64+8:
+                                    self.__chIDA = int(np.round((self.__slope*np.median(chidFifo1[-1-64-8:-1-64]) + self.__intercept) / (255.0/8)))
+                                    self.__chIDB = int(np.round((self.__slope*np.median(chidFifo2[-1-64-8:-1-64]) + self.__intercept) / (255.0/8)))
+
+                            chidFifo1 = []
+                            chidFifo2 = []
                             statecorr = 0
                         else:
                             statecorr = 0
@@ -253,7 +461,9 @@ 

Source code for directdemod.decode_noaa

                 lcorrsig = outcorrsig
 
 
-                imgLine = np.median(imgLine, axis = -1)
+                imgLineA = np.median(imgLineA, axis = -1)
+                imgLineB = np.median(imgLineB, axis = -1)
+                imgLine = np.concatenate([imgLineA, imgLineB])
 
                 if self.__slope is None or self.__intercept is None:
                     imageBuffer.append(imgLine[:])
@@ -290,6 +500,49 @@ 

Source code for directdemod.decode_noaa

 
         return self.__image
 
+    def __fillSync(self, csync, maxLen):
+        '''Filters and fills missed syncs to help generate image
+        
+        Args:
+            csync (:obj:`list`): List of detected syncs
+            maxLen (:obj:`int`): Frequency offset of source in Hz
+        
+        Returns:
+            :obj:`list`: corrected syncs
+        '''
+        syncDIff = np.diff(csync)
+        modeSyncDIff = max(set(syncDIff), key=list(syncDIff).count)
+        wiggleRoom = 200
+
+        validSyncs = []
+        for i in range(len(csync) - 1):
+            if abs(csync[i+1] - csync[i] - modeSyncDIff) < wiggleRoom:
+                if csync[i] not in validSyncs:
+                    validSyncs.append(csync[i])
+                if csync[i+1] not in validSyncs:
+                    validSyncs.append(csync[i+1])
+
+        correctedSyncs = validSyncs[:]
+
+        # initial correction
+        c = validSyncs[0] - modeSyncDIff
+        while(c > wiggleRoom):
+            correctedSyncs.append(c)
+            c -= modeSyncDIff
+
+        # later corrections
+        anchor = 0
+        c = modeSyncDIff
+        while(validSyncs[anchor] + c < maxLen):
+            if (anchor + 1) < len(validSyncs) and (abs(validSyncs[anchor + 1] - c - validSyncs[anchor]) < wiggleRoom or c + validSyncs[anchor] > validSyncs[anchor + 1]):
+                anchor += 1
+                c = modeSyncDIff
+            else:
+                correctedSyncs.append(validSyncs[anchor] + c)
+                c += modeSyncDIff
+
+        return list(np.sort(correctedSyncs))
+
     @property
     def getImageA(self):
         '''Get Image A from the extracted image
@@ -330,8 +583,15 @@ 

Source code for directdemod.decode_noaa

 
             imageA = self.getImageA
             imageB = self.getImageB
+            #imageAb = scipy.ndimage.uniform_filter(self.getImageA, size=(3, 3))
+            #imageBb = scipy.ndimage.uniform_filter(self.getImageB, size=(3, 3))
 
             # constants
+            #tempLimit = 147.0
+            #seaLimit = 25.0
+            #landLimit = 90.0
+
+            #orig
             tempLimit = 155.0
             seaLimit = 30.0
             landLimit = 90.0
@@ -341,23 +601,24 @@ 

Source code for directdemod.decode_noaa

                 colorRow = []
                 for c in range(1040):
                     v, t = imageA[r,c], imageB[r,c]
+                    #vb, tb = imageAb[r,c], imageBb[r,c]
                     maxColor, minColor = None, None
                     scaleVisible, scaleTemp = None, None
-
+                    # change to >
                     if t < tempLimit:
                         # clouds
-                        maxColor, minColor = [230, 0.2, 0.3], [230, 0.0, 1.0]
+                        minColor, maxColor = [230/360.0, 0.2, 0.3], [230/360.0, 0.0, 1.0]
                         scaleVisible = v / 256.0
                         scaleTemp = (256.0 - t) / 256.0
                     else:
                         if v < seaLimit:
                             # sea
-                            maxColor, minColor = [200.0, 0.7, 0.6], [240.0, 0.6, 0.4]
+                            minColor, maxColor = [200.0/360.0, 0.7, 0.6], [240.0/360.0, 0.6, 0.4]
                             scaleVisible = v / seaLimit
                             scaleTemp = (256.0-t) / (256.0 - tempLimit)
                         else:
                             # ground
-                            maxColor, minColor = [60.0, 0.6, 0.2], [100.0, 0.0, 0.5]
+                            minColor, maxColor = [60.0/360.0, 0.6, 0.2], [100.0/360.0, 0.0, 0.5]
                             scaleVisible = (v - seaLimit) / (landLimit - seaLimit)
                             scaleTemp = (256.0 - t) / (256.0 - tempLimit);
 
@@ -368,7 +629,7 @@ 

Source code for directdemod.decode_noaa

                     colorRow.append(pix)
 
                 colorImg.append(colorRow)
-                self.__color = np.uint8(np.array(colorImg))
+            self.__color = np.uint8(np.array(colorImg))
 
         return self.__color
 
@@ -419,7 +680,7 @@ 

Source code for directdemod.decode_noaa

         amDemdulator = demod_am.demod_am()
         amOut = comm.commSignal(sig.sampRate)
 
-        chunkerObj = chunker.chunker(sig, chunkSize = 60000*18)
+        chunkerObj = chunker.chunker(sig, chunkSize = 60000*4)
 
         for i in chunkerObj.getChunks:
 
@@ -565,6 +826,19 @@ 

Source code for directdemod.decode_noaa

             self.__syncB = self.__correlateAndFindPeaks(sig, constants.NOAA_SYNCB)
             logging.info('Done SyncB detection')
 
+            # determine if some data was found or not
+            syncAdiff = np.abs(np.diff(self.__syncA) - (self.__syncCrudeSampRate*0.5))
+            minSyncAdiff = np.min([np.max(syncAdiff[i:i+constants.NOAA_DETECTCONSSYNCSNUM]) for i in range(len(syncAdiff)-constants.NOAA_DETECTCONSSYNCSNUM+1)])
+
+            syncBdiff = np.abs(np.diff(self.__syncB) - (self.__syncCrudeSampRate*0.5))
+            minSyncBdiff = np.min([np.max(syncBdiff[i:i+constants.NOAA_DETECTCONSSYNCSNUM]) for i in range(len(syncBdiff)-constants.NOAA_DETECTCONSSYNCSNUM+1)])
+
+            if minSyncAdiff < constants.NOAA_DETECTMAXCHANGE or minSyncBdiff < constants.NOAA_DETECTMAXCHANGE:
+                logging.info('NOAA Signal was found')
+                self.__useful = 1
+            else:
+                logging.info('NOAA Signal was not found')
+
         return [self.__syncA, self.__syncB]
[docs] def getAccurateSync(self, useNormCorrelate = True): diff --git a/docs/_build/html/_modules/directdemod/source.html b/docs/_build/html/_modules/directdemod/source.html index f3e38a8..a864d97 100644 --- a/docs/_build/html/_modules/directdemod/source.html +++ b/docs/_build/html/_modules/directdemod/source.html @@ -90,7 +90,7 @@

Source code for directdemod.source

     '''
     An IQ.wav file source, typically an output recorded from SDRSHARP or other similar software
     '''
-
[docs] def __init__(self, filename): +
[docs] def __init__(self, filename, givenSampFreq = None): '''Initialize the object @@ -101,6 +101,8 @@

Source code for directdemod.source

         self.__offset = 0
         self.__sourceType = constants.SOURCE_IQWAV
         self.__sampFreq, self.__data = scipy.io.wavfile.read(filename, True)
+        if not givenSampFreq is None:
+            self.__sampFreq = givenSampFreq
         self.__actualLength = self.__data.shape[0]
         self.__length = self.__data.shape[0]
@@ -170,6 +172,97 @@

Source code for directdemod.source

         else:
             self.__length = self.__actualLength
+''' +An IQ.dat file source +The IQ dat file contains two channels, one channel for I component and the other for Q +''' +class IQdat(source): + ''' + An IQ.dat file source + ''' + def __init__(self, filename, givenSampFreq = None): + + '''Initialize the object + + Args: + filename (:obj:`str`): filename of the IQ.dat file + ''' + + self.__offset = 0 + self.__sourceType = constants.SOURCE_IQDAT + self.__data = np.memmap(filename) + self.__length = int(len(self.__data)/2) + self.__sampFreq = constants.IQ_SDRSAMPRATE + if not givenSampFreq is None: + self.__sampFreq = givenSampFreq + self.__actualLength = int(len(self.__data)/2) + + @property + def sampFreq(self): + + ''':obj:`int`: get sampling freq of source''' + + return self.__sampFreq + + @property + def sourceType(self): + + ''':obj:`int`: get source type''' + + return self.__sourceType + + @property + def length(self): + + ''':obj:`int`: get source length''' + + return self.__length + + def read(self, fromIndex, toIndex = None): + + '''Read source data + + Args: + fromIndex (:obj:`int`): starting index + toIndex (:obj:`int`, optional): ending index. If not provided, the element at location given by fromIndex is returned + + Returns: + :obj:`numpy array`: Complex IQ numbers in an array + ''' + + fromIndex += self.__offset + + if toIndex == None: + toIndex = fromIndex + 1 + else: + toIndex += self.__offset + + if fromIndex-self.__offset < 0 or toIndex-self.__offset < 0 or fromIndex-self.__offset >= self.length or toIndex-self.__offset > self.length: + raise ValueError("fromIndex and toIndex have invalid values") + + samples = (self.__data[2*fromIndex:2*toIndex:2]) + 1j * (self.__data[1+2*fromIndex:1+2*toIndex:2]) + return np.array(samples).astype("complex64") - (127.5 + 1j*127.5) + + def limitData(self, initOffset = None, finalLimit = None): + + '''Limit source data + + Args: + initOffset (:obj:`int`, optional): starting index + finalLimit (:obj:`int`, optional): ending index + + ''' + + if not initOffset is None: + self.__offset = initOffset + else: + self.__offset = 0 + + if not finalLimit is None: + self.__length = finalLimit - self.__offset + else: + self.__length = self.__actualLength + ''' Note: This is an alternative implementation, directly using np.memmap An IQ.wav file source, typically an output recorded from SDRSHARP @@ -180,7 +273,7 @@

Source code for directdemod.source

     Note: This is an alternative implementation, directly using np.memmap
     An IQ.wav file source, typically an output recorded from SDRSHARP
     '''
-    def __init__(self, filename):
+    def __init__(self, filename, givenSampFreq = None):
 
         '''Initialize the object
 
@@ -193,7 +286,9 @@ 

Source code for directdemod.source

         self.__data = np.memmap(filename, offset=44)
         self.__length = int(len(self.__data)/2)
         self.__sampFreq = constants.IQ_SDRSAMPRATE
-        self.__actualLength = self.__data.shape[0]
+        if not givenSampFreq is None:
+            self.__sampFreq = givenSampFreq
+        self.__actualLength = int(len(self.__data)/2)
 
     @property
     def sampFreq(self):
diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html
index 257c064..44d4606 100644
--- a/docs/_build/html/_modules/index.html
+++ b/docs/_build/html/_modules/index.html
@@ -37,6 +37,8 @@ 

All modules for which code is available

  • directdemod.chunker
  • directdemod.comm
  • directdemod.decode_afsk1200
  • +
  • directdemod.decode_funcube
  • +
  • directdemod.decode_meteorm2
  • directdemod.decode_noaa
  • directdemod.demod_am
  • directdemod.demod_fm
  • diff --git a/docs/_build/html/_sources/modules.rst.txt b/docs/_build/html/_sources/modules.rst.txt index 0cec619..60eca29 100644 --- a/docs/_build/html/_sources/modules.rst.txt +++ b/docs/_build/html/_sources/modules.rst.txt @@ -28,6 +28,16 @@ Specific applications .. automethod:: __init__ +.. autoclass:: directdemod.decode_funcube.decode_funcube + :members: + + .. automethod:: __init__ + +.. autoclass:: directdemod.decode_meteorm2.decode_meteorm2 + :members: + + .. automethod:: __init__ + Filters --------------- diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html index 33bf5b4..15aa5de 100644 --- a/docs/_build/html/genindex.html +++ b/docs/_build/html/genindex.html @@ -64,6 +64,10 @@

    _

  • (directdemod.comm.commSignal method)
  • (directdemod.decode_afsk1200.decode_afsk1200 method) +
  • +
  • (directdemod.decode_funcube.decode_funcube method) +
  • +
  • (directdemod.decode_meteorm2.decode_meteorm2 method)
  • (directdemod.decode_noaa.decode_noaa method)
  • @@ -132,10 +136,12 @@

    B

    C

    @@ -145,6 +151,10 @@

    D

    - +
    @@ -318,6 +336,16 @@

    U

    diff --git a/docs/_build/html/modules.html b/docs/_build/html/modules.html index a06095a..2a96b79 100644 --- a/docs/_build/html/modules.html +++ b/docs/_build/html/modules.html @@ -224,6 +224,22 @@

    Specific applications +
    +
    +channelID
    +

    get channel ID’s

    + +++ + + + + + +
    Returns:[channelIDA, channelIDB]
    Return type:list
    +
    +
    getAccurateSync(useNormCorrelate=True)[source]
    @@ -338,6 +354,42 @@

    Specific applications

    +
    +
    +getMapImage(cTime, destFileRot, destFileNoRot, satellite, tleFile=None)[source]
    +

    Get the map overlay of the image

    + +++ + + + +
    Parameters:
      +
    • cTime (datetime) – Time of start of capture in UTC
    • +
    • tleFile (str, optional) – TLE file location, pulls latest from internet if not given
    • +
    • destFile (str) – location where to store the image
    • +
    • satellite (str) – Satellite name, ex: NOAA 19 etc.
    • +
    +
    +
    + +
    +
    +useful
    +

    See if some data was found or not – 10 consecutive syncs apart by 0.5s+-error

    + +++ + + + + + +
    Returns:0 if not found, 1 if found
    Return type:int
    +
    +
    @@ -437,6 +489,136 @@

    Specific applications

    +
    +
    +useful
    +

    See if atleast one message was found or not

    + +++ + + + + + +
    Returns:0 if not found, 1 if found
    Return type:int
    +
    + + + +
    +
    +class directdemod.decode_funcube.decode_funcube(sigsrc, offset, bw)[source]
    +

    Object to decode Funcube

    +
    +
    +__init__(sigsrc, offset, bw)[source]
    +

    Initialize the object

    + +++ + + + +
    Parameters:
      +
    • sigsrc (commSignal) – IQ data source
    • +
    • offset (float) – Frequency offset of source in Hz
    • +
    • bw (int, optional) – Bandwidth
    • +
    +
    +
    + +
    +
    +getSyncs
    +

    Get syncs of Funcube

    + +++ + + + + + +
    Returns:list of detected syncs
    Return type:list
    +
    + +
    +
    +useful
    +

    See if signal was found

    + +++ + + + + + +
    Returns:0 if not found, 1 if found
    Return type:int
    +
    + +
    + +
    +
    +class directdemod.decode_meteorm2.decode_meteorm2(sigsrc, offset, bw)[source]
    +

    Object to decode Meteor m2

    +
    +
    +__init__(sigsrc, offset, bw)[source]
    +

    Initialize the object

    + +++ + + + +
    Parameters:
      +
    • sigsrc (commSignal) – IQ data source
    • +
    • offset (float) – Frequency offset of source in Hz
    • +
    • bw (int, optional) – Bandwidth
    • +
    +
    +
    + +
    +
    +getSyncs
    +

    Get syncs of Funcube

    + +++ + + + + + +
    Returns:list of detected syncs
    Return type:list
    +
    + +
    +
    +useful
    +

    See if signal was found

    + +++ + + + + + +
    Returns:0 if not found, 1 if found
    Return type:int
    +
    +

    @@ -847,11 +1029,11 @@

    Demodulators

    -class directdemod.source.IQwav(filename)[source]
    +class directdemod.source.IQwav(filename, givenSampFreq=None)[source]

    An IQ.wav file source, typically an output recorded from SDRSHARP or other similar software

    -__init__(filename)[source]
    +__init__(filename, givenSampFreq=None)[source]

    Initialize the object

    diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv index 81e147f..ef99680 100644 Binary files a/docs/_build/html/objects.inv and b/docs/_build/html/objects.inv differ diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js index 69666da..d55bdc0 100644 --- a/docs/_build/html/searchindex.js +++ b/docs/_build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["index","modules"],envversion:53,filenames:["index.rst","modules.rst"],objects:{"directdemod.chunker":{chunker:[1,0,1,""]},"directdemod.chunker.chunker":{__init__:[1,1,1,""],get:[1,1,1,""],getChunks:[1,2,1,""],set:[1,1,1,""]},"directdemod.comm":{commSignal:[1,0,1,""]},"directdemod.comm.commSignal":{__init__:[1,1,1,""],bwLim:[1,1,1,""],extend:[1,1,1,""],filter:[1,1,1,""],funcApply:[1,1,1,""],length:[1,2,1,""],offsetFreq:[1,1,1,""],sampRate:[1,2,1,""],signal:[1,2,1,""],updateSignal:[1,1,1,""]},"directdemod.decode_afsk1200":{decode_afsk1200:[1,0,1,""]},"directdemod.decode_afsk1200.decode_afsk1200":{__init__:[1,1,1,""],decode_nrzi:[1,1,1,""],find_bit_stuffing:[1,1,1,""],getMsg:[1,2,1,""],reduce_stuffed_bit:[1,1,1,""]},"directdemod.decode_noaa":{decode_noaa:[1,0,1,""]},"directdemod.decode_noaa.decode_noaa":{__init__:[1,1,1,""],getAccurateSync:[1,1,1,""],getAudio:[1,2,1,""],getColor:[1,2,1,""],getCrudeSync:[1,1,1,""],getImage:[1,2,1,""],getImageA:[1,2,1,""],getImageB:[1,2,1,""]},"directdemod.demod_am":{demod_am:[1,0,1,""],demod_amFLT:[1,0,1,""]},"directdemod.demod_am.demod_am":{demod:[1,1,1,""]},"directdemod.demod_am.demod_amFLT":{__init__:[1,1,1,""],demod:[1,1,1,""]},"directdemod.demod_fm":{demod_fm:[1,0,1,""],demod_fmAD:[1,0,1,""]},"directdemod.demod_fm.demod_fm":{__init__:[1,1,1,""],demod:[1,1,1,""]},"directdemod.demod_fm.demod_fmAD":{__init__:[1,1,1,""],demod:[1,1,1,""]},"directdemod.filters":{blackmanHarris:[1,0,1,""],blackmanHarrisConv:[1,0,1,""],butter:[1,0,1,""],filter:[1,0,1,""],gaussian:[1,0,1,""],hamming:[1,0,1,""],remez:[1,0,1,""],rollingAverage:[1,0,1,""]},"directdemod.filters.blackmanHarris":{__init__:[1,1,1,""]},"directdemod.filters.blackmanHarrisConv":{__init__:[1,1,1,""],applyOn:[1,1,1,""]},"directdemod.filters.butter":{__init__:[1,1,1,""]},"directdemod.filters.filter":{__init__:[1,1,1,""],applyOn:[1,1,1,""],getA:[1,2,1,""],getB:[1,2,1,""]},"directdemod.filters.gaussian":{__init__:[1,1,1,""]},"directdemod.filters.hamming":{__init__:[1,1,1,""]},"directdemod.filters.remez":{__init__:[1,1,1,""]},"directdemod.filters.rollingAverage":{__init__:[1,1,1,""]},"directdemod.log":{log:[1,0,1,""]},"directdemod.log.log":{__init__:[1,1,1,""]},"directdemod.sink":{image:[1,0,1,""],wavFile:[1,0,1,""]},"directdemod.sink.image":{__init__:[1,1,1,""],show:[1,2,1,""],write:[1,2,1,""]},"directdemod.sink.wavFile":{__init__:[1,1,1,""],write:[1,2,1,""]},"directdemod.source":{IQwav:[1,0,1,""]},"directdemod.source.IQwav":{__init__:[1,1,1,""],length:[1,2,1,""],limitData:[1,1,1,""],read:[1,1,1,""],sampFreq:[1,2,1,""],sourceType:[1,2,1,""]}},objnames:{"0":["py","class","Python class"],"1":["py","method","Python method"],"2":["py","attribute","Python attribute"]},objtypes:{"0":"py:class","1":"py:method","2":"py:attribute"},terms:{"case":1,"class":1,"final":[],"float":1,"function":1,"int":1,"new":1,"return":1,"true":1,Not:1,The:1,Useful:1,__init__:1,abcd:1,accur:[],add:1,added:1,afsk1200:1,all:1,altern:1,amdemod:[],amdemodflt:[],angl:1,ani:[],anoth:1,anyth:1,appli:1,applic:0,applyon:1,apt:1,arg:[],arrai:1,arraywithsignalvalu:[],audio:1,audiosig:[],autoclass:[],averag:1,avoid:1,ayth:1,band:1,bandwidth:1,bit:1,blackman:1,blackmanharri:1,blackmanharrisconv:1,bool:1,border:1,butter:1,butterworth:1,bwlim:1,call:[],chunk:0,chunker:1,chunksiz:1,code_bit:1,color:1,comm:1,commsign:1,complex:1,condit:1,consol:1,constant:1,contain:[],content:0,convolv:1,correl:1,correspond:1,creat:1,csv:[],cutoff:1,cutoffa:1,cutoffb:1,data:1,decod:1,decode_afsk1200:1,decode_noaa:1,decode_nrzi:1,delai:1,demdodul:1,demod:1,demod_am:1,demod_amflt:1,demod_fm:1,demod_fmad:1,demodul:0,descript:[],desir:1,detail:1,deviat:1,differ:1,differenti:1,diment:1,disabl:1,displai:1,downsampl:1,dtype:1,dure:1,effeci:[],effect:1,element:1,enabl:1,end:1,envelop:1,error:1,etc:[],everytim:[],exactli:1,experi:1,experiment:1,extend:1,extract:1,fals:1,file:1,filenam:1,filt:1,filter:0,finallimit:1,find:1,find_bit_stuf:1,float64:1,flt_b:1,flt_lp:1,fmdemod:[],fmdemodad:[],forc:1,form:1,free:1,freq:1,freqoffset:1,frequenc:1,from:1,fromindex:1,func:1,funcappli:1,gain:1,gaussian:1,gener:[],get:1,geta:1,getaccuratesync:1,getaudio:1,getb:1,getchunk:1,getcolor:1,getcrudesync:1,getimag:1,getimagea:1,getimageb:1,getmsg:1,given:1,going:1,ham:1,harri:1,has:1,help:1,helper:0,highest:1,hilbert:1,imag:1,implement:1,increas:1,index:[0,1],init:1,initi:1,initoffset:1,initout:1,input:1,integ:1,iqwav:1,iqwavalt:[],its:1,just:1,larg:1,length:1,limit:1,limitdata:1,line1:[],list:1,locat:1,log:0,low:1,lowpass:1,mat:1,match:1,matrix:1,member:[],memori:[],messag:1,method:1,modul:0,multipli:1,must:1,name:1,necessari:1,necessaryinput:[],need:1,noaa:1,noaa_crudesyncsampr:1,non:1,none:1,normal:1,note:1,nrzi:1,ntap:1,number:1,numpi:1,obj:[],object:0,offset:1,offsetfreq:1,one:1,option:1,optionalinput:[],order:1,other:1,output:1,overlap:1,page:0,paramet:1,parent:1,pass:1,phase:1,pixel:1,posit:[],previous:1,process:1,properti:1,provid:1,rate:1,read:1,recommend:1,record:1,reduce_stuffed_bit:1,refer:1,remez:1,remov:1,result:1,roll:1,rollingaverag:1,sai:[],sampfreq:1,sampl:1,samplingr:[],samprat:1,sdrsharp:1,search:0,see:1,self:1,set:1,show:1,sig:1,sigma:1,signal:0,signatur:[],sigsrc:1,similar:1,simpl:1,sink:0,size:1,softwar:1,sourc:0,sourcetyp:1,specif:0,standard:1,start:1,state:1,statu:1,store:1,storest:1,str:1,strict:1,string:1,stuf:1,stuffed_bit:1,sync:1,tail:1,tap:1,target:1,term:1,test:[],thi:1,toindex:1,transform:1,tsamprat:1,type:1,typeflt:1,typic:1,undefin:1,uniq:1,updat:1,updatesign:1,use:1,used:1,usenormcorrel:1,using:1,util:[],valu:1,variabl:1,wav:1,wavfil:1,when:1,whether:1,which:1,window:1,write:1,written:1,zero:1,zerophas:1},titles:["Welcome to DirectDemod\u2019s documentation!","DirectDemod: Modules documentation"],titleterms:{applic:1,chunk:1,demodul:1,directdemod:[0,1],document:[0,1],filter:1,helper:1,indic:0,log:1,modul:1,object:1,signal:1,sink:1,sourc:1,specif:1,tabl:0,util:[],welcom:0}}) \ No newline at end of file +Search.setIndex({docnames:["index","modules"],envversion:53,filenames:["index.rst","modules.rst"],objects:{"directdemod.chunker":{chunker:[1,0,1,""]},"directdemod.chunker.chunker":{__init__:[1,1,1,""],get:[1,1,1,""],getChunks:[1,2,1,""],set:[1,1,1,""]},"directdemod.comm":{commSignal:[1,0,1,""]},"directdemod.comm.commSignal":{__init__:[1,1,1,""],bwLim:[1,1,1,""],extend:[1,1,1,""],filter:[1,1,1,""],funcApply:[1,1,1,""],length:[1,2,1,""],offsetFreq:[1,1,1,""],sampRate:[1,2,1,""],signal:[1,2,1,""],updateSignal:[1,1,1,""]},"directdemod.decode_afsk1200":{decode_afsk1200:[1,0,1,""]},"directdemod.decode_afsk1200.decode_afsk1200":{__init__:[1,1,1,""],decode_nrzi:[1,1,1,""],find_bit_stuffing:[1,1,1,""],getMsg:[1,2,1,""],reduce_stuffed_bit:[1,1,1,""],useful:[1,2,1,""]},"directdemod.decode_funcube":{decode_funcube:[1,0,1,""]},"directdemod.decode_funcube.decode_funcube":{__init__:[1,1,1,""],getSyncs:[1,2,1,""],useful:[1,2,1,""]},"directdemod.decode_meteorm2":{decode_meteorm2:[1,0,1,""]},"directdemod.decode_meteorm2.decode_meteorm2":{__init__:[1,1,1,""],getSyncs:[1,2,1,""],useful:[1,2,1,""]},"directdemod.decode_noaa":{decode_noaa:[1,0,1,""]},"directdemod.decode_noaa.decode_noaa":{__init__:[1,1,1,""],channelID:[1,2,1,""],getAccurateSync:[1,1,1,""],getAudio:[1,2,1,""],getColor:[1,2,1,""],getCrudeSync:[1,1,1,""],getImage:[1,2,1,""],getImageA:[1,2,1,""],getImageB:[1,2,1,""],getMapImage:[1,1,1,""],useful:[1,2,1,""]},"directdemod.demod_am":{demod_am:[1,0,1,""],demod_amFLT:[1,0,1,""]},"directdemod.demod_am.demod_am":{demod:[1,1,1,""]},"directdemod.demod_am.demod_amFLT":{__init__:[1,1,1,""],demod:[1,1,1,""]},"directdemod.demod_fm":{demod_fm:[1,0,1,""],demod_fmAD:[1,0,1,""]},"directdemod.demod_fm.demod_fm":{__init__:[1,1,1,""],demod:[1,1,1,""]},"directdemod.demod_fm.demod_fmAD":{__init__:[1,1,1,""],demod:[1,1,1,""]},"directdemod.filters":{blackmanHarris:[1,0,1,""],blackmanHarrisConv:[1,0,1,""],butter:[1,0,1,""],filter:[1,0,1,""],gaussian:[1,0,1,""],hamming:[1,0,1,""],remez:[1,0,1,""],rollingAverage:[1,0,1,""]},"directdemod.filters.blackmanHarris":{__init__:[1,1,1,""]},"directdemod.filters.blackmanHarrisConv":{__init__:[1,1,1,""],applyOn:[1,1,1,""]},"directdemod.filters.butter":{__init__:[1,1,1,""]},"directdemod.filters.filter":{__init__:[1,1,1,""],applyOn:[1,1,1,""],getA:[1,2,1,""],getB:[1,2,1,""]},"directdemod.filters.gaussian":{__init__:[1,1,1,""]},"directdemod.filters.hamming":{__init__:[1,1,1,""]},"directdemod.filters.remez":{__init__:[1,1,1,""]},"directdemod.filters.rollingAverage":{__init__:[1,1,1,""]},"directdemod.log":{log:[1,0,1,""]},"directdemod.log.log":{__init__:[1,1,1,""]},"directdemod.sink":{image:[1,0,1,""],wavFile:[1,0,1,""]},"directdemod.sink.image":{__init__:[1,1,1,""],show:[1,2,1,""],write:[1,2,1,""]},"directdemod.sink.wavFile":{__init__:[1,1,1,""],write:[1,2,1,""]},"directdemod.source":{IQwav:[1,0,1,""]},"directdemod.source.IQwav":{__init__:[1,1,1,""],length:[1,2,1,""],limitData:[1,1,1,""],read:[1,1,1,""],sampFreq:[1,2,1,""],sourceType:[1,2,1,""]}},objnames:{"0":["py","class","Python class"],"1":["py","method","Python method"],"2":["py","attribute","Python attribute"]},objtypes:{"0":"py:class","1":"py:method","2":"py:attribute"},terms:{"case":1,"class":1,"final":[],"float":1,"function":1,"int":1,"new":1,"return":1,"true":1,Not:1,The:1,Useful:1,__init__:1,abcd:1,accur:[],add:1,added:1,afsk1200:1,all:1,altern:1,amdemod:[],amdemodflt:[],angl:1,ani:[],anoth:1,anyth:1,apart:1,appli:1,applic:0,applyon:1,apt:1,arg:[],arrai:1,arraywithsignalvalu:[],atleast:1,audio:1,audiosig:[],autoclass:[],averag:1,avoid:1,ayth:1,band:1,bandwidth:1,bit:1,blackman:1,blackmanharri:1,blackmanharrisconv:1,bool:1,border:1,butter:1,butterworth:1,bwlim:1,call:[],captur:1,channel:1,channelid:1,channelida:1,channelidb:1,chunk:0,chunker:1,chunksiz:1,code_bit:1,color:1,comm:1,commsign:1,complex:1,condit:1,consecut:1,consol:1,constant:1,contain:[],content:0,convolv:1,correl:1,correspond:1,creat:1,csv:[],ctime:1,cutoff:1,cutoffa:1,cutoffb:1,data:1,datetim:1,decod:1,decode_afsk1200:1,decode_funcub:1,decode_meteorm2:1,decode_noaa:1,decode_nrzi:1,delai:1,demdodul:1,demod:1,demod_am:1,demod_amflt:1,demod_fm:1,demod_fmad:1,demodul:0,descript:[],desir:1,destfil:1,destfilenorot:1,destfilerot:1,detail:1,detect:1,deviat:1,differ:1,differenti:1,diment:1,disabl:1,displai:1,downsampl:1,dtype:1,dure:1,effeci:[],effect:1,element:1,enabl:1,end:1,envelop:1,error:1,etc:1,everytim:[],exactli:1,experi:1,experiment:1,extend:1,extract:1,fals:1,file:1,filenam:1,filt:1,filter:0,finallimit:1,find:1,find_bit_stuf:1,float64:1,flt_b:1,flt_lp:1,fmdemod:[],fmdemodad:[],forc:1,form:1,found:1,free:1,freq:1,freqoffset:1,frequenc:1,from:1,fromindex:1,func:1,funcappli:1,funcub:1,gain:1,gaussian:1,gener:[],get:1,geta:1,getaccuratesync:1,getaudio:1,getb:1,getchunk:1,getcolor:1,getcrudesync:1,getimag:1,getimagea:1,getimageb:1,getmapimag:1,getmsg:1,getsync:1,given:1,givensampfreq:1,going:1,ham:1,harri:1,has:1,help:1,helper:0,highest:1,hilbert:1,imag:1,implement:1,increas:1,index:[0,1],init:1,initi:1,initoffset:1,initout:1,input:1,integ:1,internet:1,iqwav:1,iqwavalt:[],its:1,just:1,larg:1,latest:1,length:1,limit:1,limitdata:1,line1:[],list:1,locat:1,log:0,low:1,lowpass:1,map:1,mat:1,match:1,matrix:1,member:[],memori:[],messag:1,meteor:1,method:1,modul:0,multipli:1,must:1,name:1,necessari:1,necessaryinput:[],need:1,noaa:1,noaa_crudesyncsampr:1,non:1,none:1,normal:1,note:1,nrzi:1,ntap:1,number:1,numpi:1,obj:[],object:0,offset:1,offsetfreq:1,one:1,option:1,optionalinput:[],order:1,other:1,output:1,overlai:1,overlap:1,page:0,paramet:1,parent:1,pass:1,phase:1,pixel:1,posit:[],previous:1,process:1,properti:1,provid:1,pull:1,rate:1,read:1,recommend:1,record:1,reduce_stuffed_bit:1,refer:1,remez:1,remov:1,result:1,roll:1,rollingaverag:1,sai:[],sampfreq:1,sampl:1,samplingr:[],samprat:1,satellit:1,sdrsharp:1,search:0,see:1,self:1,set:1,show:1,sig:1,sigma:1,signal:0,signatur:[],sigsrc:1,similar:1,simpl:1,sink:0,size:1,softwar:1,some:1,sourc:0,sourcetyp:1,specif:0,standard:1,start:1,state:1,statu:1,store:1,storest:1,str:1,strict:1,string:1,stuf:1,stuffed_bit:1,sync:1,tail:1,tap:1,target:1,term:1,test:[],thi:1,time:1,tle:1,tlefil:1,toindex:1,transform:1,tsamprat:1,type:1,typeflt:1,typic:1,undefin:1,uniq:1,updat:1,updatesign:1,use:1,used:1,useful:1,usenormcorrel:1,using:1,utc:1,util:[],valu:1,variabl:1,wav:1,wavfil:1,when:1,where:1,whether:1,which:1,window:1,write:1,written:1,zero:1,zerophas:1},titles:["Welcome to DirectDemod\u2019s documentation!","DirectDemod: Modules documentation"],titleterms:{applic:1,chunk:1,demodul:1,directdemod:[0,1],document:[0,1],filter:1,helper:1,indic:0,log:1,modul:1,object:1,signal:1,sink:1,sourc:1,specif:1,tabl:0,util:[],welcom:0}}) \ No newline at end of file diff --git a/docs/modules.rst b/docs/modules.rst index 0cec619..60eca29 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -28,6 +28,16 @@ Specific applications .. automethod:: __init__ +.. autoclass:: directdemod.decode_funcube.decode_funcube + :members: + + .. automethod:: __init__ + +.. autoclass:: directdemod.decode_meteorm2.decode_meteorm2 + :members: + + .. automethod:: __init__ + Filters --------------- diff --git a/main.py b/main.py index 8e29603..6091af9 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ noaa commandline interface ''' -from directdemod import source, chunker, comm, constants, filters, demod_fm, sink, demod_am, decode_noaa, log, decode_afsk1200 +from directdemod import source, chunker, comm, constants, filters, demod_fm, sink, demod_am, decode_noaa, log, decode_afsk1200, decode_funcube, decode_meteorm2 import numpy as np import sys, getopt, logging, json from time import gmtime, strftime @@ -48,6 +48,8 @@ def usage(err = ""): print("\t\t--tle= : TLE source filename") print("\t\t-noimage : doesn't show/store image") print("\t-d afsk1200 : AFSK1200 decoder") + print("\t-d funcube : Funcube BPSK sync detector") + print("\t-d meteor : Meteor QPSK sync detector") print() exit() @@ -280,6 +282,56 @@ def usage(err = ""): entryDict['usefulness'] = afskObj.useful + # if Funcube BPSK was chosen + elif decoders[fileIndex] == "funcube": + logging.info('Detecting Funcube Syncs') + + entryDict['filesCreated'] = [] + + # create funcube object + funcubeObj = decode_funcube.decode_funcube(sigsrc, freqOffset, bandwidths[fileIndex]) + syncs = funcubeObj.getSyncs + + #print results + logging.info('Complete: detected %d syncs', len(syncs)) + + # write syncs + csvFileName = fileName.split(".")[0] + "_f" + str(fileIndex+1) + ".csv" + if not outs[fileIndex] is None: + csvFileName = outs[fileIndex] + ".csv" + + sink.csv(csvFileName, [syncs], titles = ["Funcube syncs"]).write + entryDict['filesCreated'].append(csvFileName) + + logging.info('CSV file successfully created') + + entryDict['usefulness'] = funcubeObj.useful + + # if Meteor m2 QPSK was chosen + elif decoders[fileIndex] == "meteor": + logging.info('Detecting Meteor M2 Syncs') + + entryDict['filesCreated'] = [] + + # create meteor object + meteorObj = decode_meteorm2.decode_meteorm2(sigsrc, freqOffset, bandwidths[fileIndex]) + syncs = meteorObj.getSyncs + + #print results + logging.info('Complete: detected %d syncs', len(syncs)) + + # write syncs + csvFileName = fileName.split(".")[0] + "_f" + str(fileIndex+1) + ".csv" + if not outs[fileIndex] is None: + csvFileName = outs[fileIndex] + ".csv" + + sink.csv(csvFileName, [syncs], titles = ["Meteor syncs"]).write + entryDict['filesCreated'].append(csvFileName) + + logging.info('CSV file successfully created') + + entryDict['usefulness'] = meteorObj.useful + else: usage("Invalid decoder selected")