def GetAverage(self, nStart, nEnd): """Return a SweepData object with average data Parameters: nStart -- Start frequency for the average calculation nEnd -- End frequency for the average calculation Returns: RFESweepData object with average data, None otherwise """ #string sDebugText = "" if (nStart > self.m_nUpperBound or nEnd > self.m_nUpperBound or nStart > nEnd): return None try: objReturn = RFESweepData(self.m_arrData[nEnd].StartFrequencyMHZ, self.m_arrData[nEnd].StepFrequencyMHZ, self.m_arrData[nEnd].TotalSteps) for nSweepInd in objReturn.TotalSteps: #sDebugText += "[" + nSweepInd + "]:" fSweepValue = 0.0 nIterationInd = nStart while (nIterationInd <= nEnd): if (nSweepInd == 0): #check all the sweeps use the same configuration, but #only in first loop to reduce overhead if (not self.m_arrData[nIterationInd]. IsSameConfiguration(objReturn)): return None fSweepValue += self.m_arrData[ nIterationInd].GetAmplitudeDBM(nSweepInd, None, False) #sDebugText += #str(self.m_arrData[nIterationInd].GetAmplitudeDBM(nSweepInd)) #+ "," nIterationInd += 1 fSweepValue = fSweepValue / (nEnd - nStart + 1) #sDebugText += "(" + str(fSweepValue) + ")" objReturn.SetAmplitudeDBM(nSweepInd, fSweepValue) except Exception as obEx: objReturn = None print("Error in RFESweedDataCollection - GetAverage(): " + str(obEx)) return objReturn
def Add(self, SweepData): """This function add a single sweep data in the collection Parameters: SweepData -- A single sweep data Returns: Boolean True it sweep data is added, False otherwise """ try: if (self.IsFull()): return False if (not self.m_MaxHoldData): self.m_MaxHoldData = RFESweepData(SweepData.StartFrequencyMHZ, SweepData.StepFrequencyMHZ, SweepData.TotalSteps) if (self.m_nUpperBound >= (len(self.m_arrData) - 1)): if (self.m_bAutogrow): self.ResizeCollection(10 * 1000) #add 10K samples more else: #move all items one position down, lose the older one at position 0 self.m_nUpperBound = len(self.m_arrData) - 2 self.m_arrData[0] = None for nInd in range(self.m_nUpperBound): self.m_arrData[nInd] = self.m_arrData[nInd + 1] self.m_nUpperBound += 1 self.m_arrData[self.m_nUpperBound] = SweepData nInd = 0 while nInd < SweepData.TotalSteps: if (SweepData.GetAmplitudeDBM(nInd, None, False) > self.m_MaxHoldData.GetAmplitudeDBM(nInd, None, False)): self.m_MaxHoldData.SetAmplitudeDBM( nInd, SweepData.GetAmplitudeDBM(nInd, None, False)) nInd += 1 except Exception as obEx: print("Error in RFESweepDataCollection - Add(): " + str(obEx)) return False return True
class RFESweepDataCollection: """ Allocates up to nCollectionSize elements to start with the container. """ def __init__(self, nCollectionSize, bAutogrow): self.m_arrData = [] #Collection of available spectrum data items self.m_MaxHoldData = None #Single data set, defined for the whole collection and updated with Add, to #keep the Max Hold values self.m_nUpperBound = -1 #Max value for index with available data self.m_nInitialCollectionSize = 0 if (nCollectionSize > RFE_Common.CONST_MAX_ELEMENTS): nCollectionSize = RFE_Common.CONST_MAX_ELEMENTS self.m_bAutogrow = bAutogrow #true if the array bounds may grow up to _MAX_ELEMENTS, otherwise will #be limited to initial collection size self.m_nInitialCollectionSize = nCollectionSize self.CleanAll() @property def MaxHoldData(self): """Single data set, defined for the whole collection and updated with Add, to keep the Max Hold values """ return self.m_MaxHoldData @property def Count(self): """ Returns the total of elements with actual data allocated. """ return int(self.m_nUpperBound + 1) @property def UpperBound(self): """ Returns the highest valid index of elements with actual data allocated. """ return self.m_nUpperBound @classmethod def FileHeaderVersioned_001(cls): """File format constant - 001 Returns: String 001 File header version """ return "RFExplorer PC Client - Format v001" @classmethod def FileHeaderVersioned(cls): """File format constant indicates the latest known and supported file format Returns: String File header version """ return "RFExplorer PC Client - Format v" + "{:03d}".format( RFE_Common.CONST_FILE_VERSION) def GetData(self, nIndex): """ Return the data pointed by the zero-starting index Parameters: nIndex -- Index to find specific data inside the data array Returns: RFESweepData None if no data is available with this index """ if (nIndex <= self.m_nUpperBound): return self.m_arrData[nIndex] else: return None def IsFull(self): """ True when the absolute maximum of allowed elements in the container is allocated Returns: Boolean True when the absolute maximum of allowed elements in the container is allocated, False otherwise """ return (self.m_nUpperBound >= RFE_Common.CONST_MAX_ELEMENTS) def Add(self, SweepData): """This function add a single sweep data in the collection Parameters: SweepData -- A single sweep data Returns: Boolean True it sweep data is added, False otherwise """ try: if (self.IsFull()): return False if (not self.m_MaxHoldData): self.m_MaxHoldData = RFESweepData(SweepData.StartFrequencyMHZ, SweepData.StepFrequencyMHZ, SweepData.TotalSteps) if (self.m_nUpperBound >= (len(self.m_arrData) - 1)): if (self.m_bAutogrow): self.ResizeCollection(10 * 1000) #add 10K samples more else: #move all items one position down, lose the older one at position 0 self.m_nUpperBound = len(self.m_arrData) - 2 self.m_arrData[0] = None for nInd in self.m_nUpperBound: self.m_arrData[nInd] = self.m_arrData[nInd + 1] self.m_nUpperBound += 1 self.m_arrData[self.m_nUpperBound] = SweepData nInd = 0 while nInd < SweepData.TotalSteps: if (SweepData.GetAmplitudeDBM(nInd, None, False) > self.m_MaxHoldData.GetAmplitudeDBM(nInd, None, False)): self.m_MaxHoldData.SetAmplitudeDBM( nInd, SweepData.GetAmplitudeDBM(nInd, None, False)) nInd += 1 except Exception as obEx: print("Error in RFESweepDataCollection - Add(): " + str(obEx)) return False return True def CleanAll(self): """Initialize internal data """ self.m_arrData = [RFESweepData] * self.m_nInitialCollectionSize self.m_MaxHoldData = None self.m_nUpperBound = -1 def Dump(self): """Dump a CSV string line with sweep data collection Returns: String All sweep data collection """ sDump = "" for nIndex in range(self.Count): objSweep = self.m_arrData[nIndex] if (sDump != ""): sDump += '\n' if (objSweep): sDump += objSweep.Dump() else: sDump += "Sweep {null}" return sDump def GetMedianAverage(self, nStart, nEnd): """Return a SweepData object with median average data Parameters: nStart -- Start frequency for the median average calculation nEnd -- End frequency for the median average calculation Returns: RFESweepData object with median average data, None otherwise """ #string sDebugText = "" if (nStart > self.m_nUpperBound or nEnd > self.m_nUpperBound or nStart > nEnd): return None nTotalIterations = nEnd - nStart + 1 try: objReturn = RFESweepData(self.m_arrData[nEnd].StartFrequencyMHZ, self.m_arrData[nEnd].StepFrequencyMHZ, self.m_arrData[nEnd].TotalSteps) for nSweepInd in objReturn.TotalSteps: #sDebugText += "[" + nSweepInd + "]:" fSweepValue = 0.0 arrSweepValues = [0.0] * nTotalIterations nIterationInd = nStart while (nIterationInd <= nEnd): if (nSweepInd == 0): #check all the sweeps use the same configuration, but #only in first loop to reduce overhead if (not self.m_arrData[nIterationInd]. IsSameConfiguration(objReturn)): return None arrSweepValues[nIterationInd - nStart] = self.m_arrData[ nIterationInd].GetAmplitudeDBM(nSweepInd, None, False) #sDebugText += str(self.m_arrData[nIterationInd].GetAmplitudeDBM(nSweepInd)) + "," nIterationInd += 1 arrSweepValues.sort() fSweepValue = arrSweepValues[nTotalIterations / 2] #sDebugText += "(" + str(fSweepValue) + ")" objReturn.SetAmplitudeDBM(nSweepInd, fSweepValue) except Exception as obEx: print("Error in RFESweedDataCollection - GetMedianAverage(): " + str(obEx)) objReturn = None return objReturn def GetAverage(self, nStart, nEnd): """Return a SweepData object with average data Parameters: nStart -- Start frequency for the average calculation nEnd -- End frequency for the average calculation Returns: RFESweepData object with average data, None otherwise """ #string sDebugText = "" if (nStart > self.m_nUpperBound or nEnd > self.m_nUpperBound or nStart > nEnd): return None try: objReturn = RFESweepData(self.m_arrData[nEnd].StartFrequencyMHZ, self.m_arrData[nEnd].StepFrequencyMHZ, self.m_arrData[nEnd].TotalSteps) for nSweepInd in objReturn.TotalSteps: #sDebugText += "[" + nSweepInd + "]:" fSweepValue = 0.0 nIterationInd = nStart while (nIterationInd <= nEnd): if (nSweepInd == 0): #check all the sweeps use the same configuration, but #only in first loop to reduce overhead if (not self.m_arrData[nIterationInd]. IsSameConfiguration(objReturn)): return None fSweepValue += self.m_arrData[ nIterationInd].GetAmplitudeDBM(nSweepInd, None, False) #sDebugText += #str(self.m_arrData[nIterationInd].GetAmplitudeDBM(nSweepInd)) #+ "," nIterationInd += 1 fSweepValue = fSweepValue / (nEnd - nStart + 1) #sDebugText += "(" + str(fSweepValue) + ")" objReturn.SetAmplitudeDBM(nSweepInd, fSweepValue) except Exception as obEx: objReturn = None print("Error in RFESweedDataCollection - GetAverage(): " + str(obEx)) return objReturn def SaveFileCSV(self, sFilename, cCSVDelimiter, AmplitudeCorrection): """Will write large, complex, multi-sweep CSV file. No save anything, if there are no data Parameters: sFilename -- Full path filename cCSVDelimiter -- Comma delimiter to use AmplitudeCorrection -- Optional parameter, can be None. If different than None, use the amplitude correction table """ if (self.m_nUpperBound <= 0): return objFirst = self.m_arrData[0] try: with open(sFilename, 'w') as objWriter: objWriter.write("RF Explorer CSV data file: " + self.FileHeaderVersioned() + '\n' + \ "Start Frequency: " + str(objFirst.StartFrequencyMHZ) + "MHZ" + '\n' + \ "Step Frequency: " + str(objFirst.StepFrequencyMHZ * 1000) + "KHZ" + '\n' + \ "Total data entries: " + str(self.m_nUpperBound) + '\n' + \ "Steps per entry: " + str(objFirst.TotalSteps)+ '\n') sHeader = "Sweep" + cCSVDelimiter + "Date" + cCSVDelimiter + "Time" + cCSVDelimiter + "Milliseconds" for nStep in range(objFirst.TotalSteps): dFrequency = objFirst.StartFrequencyMHZ + nStep * ( objFirst.StepFrequencyMHZ) sHeader += cCSVDelimiter + "{:08.3f}".format(dFrequency) objWriter.write(sHeader + '\n') for nSweepInd in range(self.m_nUpperBound): objWriter.write(str(nSweepInd) + cCSVDelimiter) objWriter.write(str(self.m_arrData[nSweepInd].CaptureTime.date()) + cCSVDelimiter + \ str(self.m_arrData[nSweepInd].CaptureTime.time())[:-7] + cCSVDelimiter + \ '.' +'{:03}'.format(int(str(getattr(self.m_arrData[nSweepInd].CaptureTime.time(), 'microsecond'))[:-3])) + cCSVDelimiter) if (not self.m_arrData[nSweepInd].IsSameConfiguration( objFirst)): break for nStep in range(objFirst.TotalSteps): objWriter.write( str(self.m_arrData[nSweepInd].GetAmplitudeDBM( nStep, AmplitudeCorrection, (AmplitudeCorrection != None)))) if (nStep != (objFirst.TotalSteps - 1)): objWriter.write(cCSVDelimiter) objWriter.write('\n') except Exception as objEx: print("Error in RFESweepDataCollection - SaveFileCSV(): " + str(objEx)) def GetTopBottomDataRange(self, dTopRangeDBM, dBottomRangeDBM, AmplitudeCorrection): """Return estimated Top and Bottom level using data collection, no return anything if sweep data collection is empty Parameters: dTopRangeDBM -- Bottom level in dBm dBottomRangeDBM -- Top level in dBm AmplitudeCorrection -- Optional parameter, can be None. If different than None, use the amplitude correction table Returns: Float Bottom level in dBm Float Top level in dBm """ dTopRangeDBM = RFE_Common.CONST_MIN_AMPLITUDE_DBM dBottomRangeDBM = RFE_Common.CONST_MAX_AMPLITUDE_DBM if (self.m_nUpperBound <= 0): return for nIndSample in self.m_nUpperBound: for nIndStep in self.m_arrData[0].TotalSteps: dValueDBM = self.m_arrData[nIndSample].GetAmplitudeDBM( nIndStep, AmplitudeCorrection, (AmplitudeCorrection != None)) if (dTopRangeDBM < dValueDBM): dTopRangeDBM = dValueDBM if (dBottomRangeDBM > dValueDBM): dBottomRangeDBM = dValueDBM return dTopRangeDBM, dBottomRangeDBM def ResizeCollection(self, nSizeToAdd): """Change data collection size Parameters: nSizeToAdd -- Number of sample to add to the sweep data collection """ self.m_arrData += [RFESweepData] * nSizeToAdd
def ReceiveThreadfunc(self): """Where all data coming from the device are processed and queued """ nBytes = 0 nTotalSpectrumDataDumps = 0 while self.m_objRFECommunicator.RunReceiveThread: strReceived = "" while (self.m_objRFECommunicator.PortConnected and self.m_objRFECommunicator.RunReceiveThread): sNewText = "" self.m_hSerialPortLock.acquire() try: if (self.m_objSerialPort.is_open): #print("port open") nBytes = self.m_objSerialPort.in_waiting if (nBytes > 0): sNewText = self.m_objSerialPort.read( nBytes).decode("latin_1") except Exception as obEx: print("Serial port Exception: " + str(obEx)) finally: self.m_hSerialPortLock.release() if (len(sNewText) > 0): if (self.m_objRFECommunicator.VerboseLevel > 5): print(sNewText) strReceived += str(sNewText) sNewText = "" if (len(strReceived) > 66 * 1024): #Safety code, some error prevented the string from being processed in several loop cycles.Reset it. if (self.m_objRFECommunicator.VerboseLevel > 5): print("Received string truncated (" + len(strReceived) + ")") strReceived = "" nLen = len(strReceived) if (nLen > 1): if (self.m_objRFECommunicator.VerboseLevel > 9): print(strReceived) if (strReceived[0] == '#'): nEndPos = strReceived.find("\r\n") if (nEndPos >= 0): sNewLine = strReceived[:nEndPos] sLeftOver = strReceived[nEndPos + 2:] strReceived = sLeftOver #print(sNewLine) if ((len(sNewLine) > 5) and ((sNewLine[:6] == "#C2-F:") or ((sNewLine[:4] == "#C3-") and (sNewLine[4] != 'M'))) or sNewLine.startswith("#C4-F:")): if (self.m_objRFECommunicator.VerboseLevel > 5): print("Received Config:" + str(len(strReceived))) #Standard configuration expected objNewConfiguration = RFEConfiguration(None) #print("sNewLine: "+ sNewLine) if (objNewConfiguration.ProcessReceivedString( sNewLine)): self.m_objCurrentConfiguration = RFEConfiguration( objNewConfiguration) self.m_hQueueLock.acquire() self.m_objQueue.put(objNewConfiguration) self.m_hQueueLock.release() else: self.m_hQueueLock.acquire() self.m_objQueue.put(sNewLine) self.m_hQueueLock.release() elif (strReceived[0] == '$'): if (nLen > 4 and (strReceived[1] == 'C')): nSize = 2 #account for cr+lf #calibration data if (strReceived[2] == 'c'): nSize += int(strReceived[3]) + 4 elif (strReceived[2] == 'b'): nSize += (int(strReceived[4]) + 1) * 16 + 10 if (nSize > 2 and nLen >= nSize): nEndPos = strReceived.find("\r\n") sNewLine = strReceived[:nEndPos] sLeftOver = strReceived[nEndPos:] strReceived = sLeftOver #print(" [" + " ".join("{:02X}".format(ord(c)) for #c in sNewLine) + "]") self.m_hQueueLock.acquire() self.m_objQueue.put(sNewLine) self.m_hQueueLock.release() elif (nLen > 2 and ((strReceived[1] == 'q') or (strReceived[1] == 'Q'))): #this is internal calibration data dump nReceivedLength = ord(strReceived[2]) nExtraLength = 3 if (strReceived[1] == 'Q'): nReceivedLength += int(0x100 * strReceived[3]) nExtraLength = 4 bLengthOK = (len(strReceived) >= (nExtraLength + nReceivedLength + 2)) if (bLengthOK): self.m_hQueueLock.acquire() self.m_objQueue.put(strReceived) self.m_hQueueLock.release() strReceived = strReceived[(nExtraLength + nReceivedLength + 2):] elif (nLen > 1 and (strReceived[1] == 'D')): #This is dump screen data if (self.m_objRFECommunicator.VerboseLevel > 5): print("Received $D" + len(strReceived)) if (len(strReceived) >= (4 + 128 * 8)): pass elif (nLen > 2 and ((strReceived[1] == "S") or (strReceived[1] == 's') or (strReceived[1] == 'z'))): #Standard spectrum analyzer data nReceivedLength = ord(strReceived[2]) nSizeChars = 3 if (strReceived[1] == 's'): if (nReceivedLength == 0): nReceivedLength = 256 nReceivedLength *= 16 elif (strReceived[1] == 'z'): nReceivedLength *= 256 nReceivedLength += ord(strReceived[3]) nSizeChars += 1 if (self.m_objRFECommunicator.VerboseLevel > 9): print("Spectrum data: " + str(nReceivedLength) + " " + str(nLen)) bLengthOK = ( nLen >= (nSizeChars + nReceivedLength + 2) ) #OK if received data >= header command($S,$s or $z) + data length + end of line('\n\r') bFullStringOK = False if ( bLengthOK ): ## Ok if data length are ok and end of line('\r\n') is in the correct place ## (at the end). Prevents corrupted data bFullStringOK = bLengthOK and ( ord(strReceived[nSizeChars + nReceivedLength:][0]) == ord('\r')) and (ord( strReceived[nSizeChars + nReceivedLength:][1]) == ord('\n')) if (self.m_objRFECommunicator.VerboseLevel > 9): print("bLengthOK " + str(bLengthOK) + "bFullStringOK " + str(bFullStringOK) + " " + str( ord(strReceived[nSizeChars + nReceivedLength:] [1])) + " - " + strReceived[nSizeChars + nReceivedLength:]) if (bFullStringOK): nTotalSpectrumDataDumps += 1 if (self.m_objRFECommunicator.VerboseLevel > 9): print("Full dump received: " + str(nTotalSpectrumDataDumps)) #So we are here because received the full set of chars expected, and all them are apparently of valid characters if (nReceivedLength <= RFE_Common.CONST_MAX_SPECTRUM_STEPS): sNewLine = "$S" + strReceived[nSizeChars:( nSizeChars + nReceivedLength)] if (self.m_objRFECommunicator.VerboseLevel > 9): print("New line:\n" + " [" + "".join("{:02X}".format(ord(c)) for c in sNewLine) + "]") if (self.m_objCurrentConfiguration): nSweepSteps = self.m_objCurrentConfiguration.nFreqSpectrumSteps objSweep = RFESweepData( self.m_objCurrentConfiguration. fStartMHZ, self. m_objCurrentConfiguration.fStepMHZ, nSweepSteps) if (objSweep.ProcessReceivedString( sNewLine, self.m_objCurrentConfiguration. fOffset_dB, self.m_objRFECommunicator. UseByteBLOB, self.m_objRFECommunicator. UseStringBLOB)): if (self.m_objRFECommunicator. VerboseLevel > 5): print(objSweep.Dump()) if ( nSweepSteps > 5 ): #check this is not an incomplete scan (perhaps from a stopped SNA tracking step) #Normal spectrum analyzer sweep data self.m_hQueueLock.acquire() self.m_objQueue.put(objSweep) self.m_hQueueLock.release() else: self.m_hQueueLock.acquire() self.m_objQueue.put(sNewLine) self.m_hQueueLock.release() else: if (self.m_objRFECommunicator. VerboseLevel > 5): print( "Configuration not available yet. $S string ignored." ) else: self.m_hQueueLock.acquire() self.m_objQueue.put( "Ignored $S of size " + str(nReceivedLength) + " expected " + str(self.m_objCurrentConfiguration. nFreqSpectrumSteps)) self.m_hQueueLock.release() strReceived = strReceived[(nSizeChars + nReceivedLength + 2):] if (self.m_objRFECommunicator.VerboseLevel > 5): sText = "New String: " nLength = len(strReceived) if (nLength > 10): nLength = 10 if (nLength > 0): sText += strReceived[:nLength] print(sText) elif (bLengthOK): #So we are here because the string doesn't end with the expected chars, but has the right length. #The most likely cause is a truncated string was received, and some chars are from next string, not #this one therefore we truncate the line to avoid being much larger, and start over again next time. nPosNextLine = strReceived.index("\r\n") if (nPosNextLine >= 0): strReceived = strReceived[nPosNextLine + 2:] else: nEndPos = strReceived.find("\r\n") if (nEndPos >= 0): sNewLine = strReceived[:nEndPos] sLeftOver = strReceived[nEndPos + 2:] strReceived = sLeftOver self.m_hQueueLock.acquire() self.m_objQueue.put(sNewLine) self.m_hQueueLock.release() if (self.m_objRFECommunicator.VerboseLevel > 9): print("sNewLine: " + sNewLine) elif (self.m_objRFECommunicator.VerboseLevel > 5): print("DEBUG partial:" + strReceived) if (self.m_objRFECommunicator.Mode != RFE_Common.eMode.MODE_TRACKING): time.sleep(0.01) time.sleep(0.5)