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 @@
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 ( - 1 j * 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 ( - 1 j * 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 ( - 1 j * 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 ( - 1 j * 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 + 1 j * 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 ]) + 1 j * ( self . __data [ 1 + 2 * fromIndex : 1 + 2 * toIndex : 2 ])
+
return np . array ( samples ) . astype ( "complex64" ) - ( 127.5 + 1 j * 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
@@ -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")