├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── bit_list_utilities.py ├── build_baseband.py ├── crc_custom.py ├── kbd_utilities.py ├── rf_const.py ├── rf_demod.py ├── rf_file_handler.py ├── rf_mod.py ├── rf_ntsc.py └── zmq_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Paul Clark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rf_utilities 2 | A collection of Python code, useful for RF work 3 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulgclark/rf_utilities/6da344387b61e33cbf47f1cbdc891daa82505127/__init__.py -------------------------------------------------------------------------------- /bit_list_utilities.py: -------------------------------------------------------------------------------- 1 | # The following functions simplify manipulating lists of bits, 2 | # in which each list item is an integer 0 or 1. This is the 3 | # primary way in which binary data is stored and manipulated 4 | # within the wave* family of tools. 5 | # 6 | # note: these functions have not been optimized for performance, 7 | # but are instead implemented for maximal clarity and readability 8 | 9 | import sys 10 | from collections import deque 11 | LOGIC_X = "X" 12 | 13 | # inverts a single bit, in which the bit is stored as an int 14 | # equal to 0 or 1 15 | def invertBit(inputBit): 16 | if inputBit == 1: 17 | outputBit = 0 18 | elif inputBit == 0: 19 | outputBit = 1 20 | else: 21 | outputBit = LOGIC_X 22 | return outputBit 23 | 24 | 25 | # logically inverts each bit in the list 26 | def invertBitList(bitList): 27 | bitListInv = [] 28 | for bit in bitList: 29 | # bitListInv.append(invertBit(bit)) 30 | if bit == 0: 31 | bitListInv.append(1) 32 | else: 33 | bitListInv.append(0) 34 | return bitListInv 35 | 36 | 37 | # rotates the bitlist, with optional inversion 38 | # positive rotate value denotes right-ward direction 39 | def rotateBitList(bitList, rotate, invert = True): 40 | # rotate 41 | ind = deque(bitList) 42 | ind.rotate(rotate) 43 | # convert to list and invert contents into output string 44 | bitList = list(ind) 45 | outList = [] 46 | if invert: 47 | #outList = invertBitList(bitList) 48 | for bit in bitList: 49 | if bit == 0: 50 | outList.append(1) 51 | else: 52 | outList.append(0) 53 | 54 | return outList 55 | 56 | 57 | # swaps the bytes of a bit list and returns the new list 58 | # should add the ability to handle lengths other than 16 59 | def byteReverse(bitList): 60 | if len(bitList) != 16: 61 | print("WARNING: input bit list not evenly divisible by 8") 62 | return 16*[LOGIC_X] 63 | else: 64 | return bitList[8:16] + bitList[0:8] 65 | 66 | 67 | # This function returns a list of bits located at the indices contained in 68 | # the index list. It will return the LOGIC_X character if the bit is outside 69 | # the bounds of the rawData list 70 | def extractDisjointedBits(targetList, indexList): 71 | extractedBits = [] 72 | for index in indexList: 73 | if index < len(targetList): 74 | extractedBits.append(targetList[index]) 75 | else: 76 | extractedBits.append(LOGIC_X) 77 | return extractedBits 78 | 79 | 80 | # writes the data value into an existing list at the indices 81 | # specified; the targetList is passed by reference, and its 82 | # contents are modified 83 | # 84 | # Note: newData and indexList must be the same length 85 | def writeDisjointBits(targetList, newData, indexList): 86 | if len(newData) != len(indexList): 87 | print("ERROR: attempting to write bits to list with", end=' ') 88 | print("improper indexList length. Exiting...") 89 | exit(1) 90 | 91 | l = len(targetList) 92 | if max(indexList) >= l: 93 | print("ERROR: index in list out of range, write will", end=' ') 94 | print("not be attempted.") 95 | return 0 96 | 97 | for index, bit in zip(indexList, newData): 98 | targetList[index] = bit 99 | 100 | 101 | # returns the decimal value of the binary value represented by 102 | # the bit list; by default, the function assumes MSB, in which 103 | # the item at index 0 is the most significant bit. You can choose 104 | # an LSB conversion by setting reverse to True. You can also 105 | # invert the bits before the conversion 106 | def bitsToDec(bitList, invert = False, reverse = False): 107 | # invert bits if necessary 108 | bitList2 = [] 109 | if invert: 110 | for bit in bitList: 111 | if bit == 0: 112 | bitList2.append(1) 113 | else: 114 | bitList2.append(0) 115 | else: 116 | bitList2 = bitList[:] 117 | 118 | # reverse bits if necessary 119 | if reverse: 120 | bitList3 = reversed(bitList2) 121 | else: 122 | bitList3 = bitList2[:] 123 | 124 | value = 0 125 | for bit in bitList3: 126 | if isinstance(bit, int): 127 | value = (value << 1) | bit 128 | else: 129 | # if we don't have an integer, then we ended up with a 130 | # logic error at some point 131 | value = -1 132 | break 133 | return int(value) 134 | 135 | 136 | # returns a bit list of the specified length, corresponding to 137 | # the integer value passed; the input integer must be greater than 138 | # or equal to zero and less than 2**(len) 139 | def decToPaddedBits(intVal, numBits): 140 | # make sure the input value is an int 141 | val = int(intVal) 142 | #if int(intVal) > (2**(numBits)-1): 143 | if val.bit_length() > numBits: 144 | print("WARNING: decToBits() passed too few bits ({}) to render integer: {}".format(numBits, val), end=' ') 145 | return numBits*[LOGIC_X] 146 | # build minimum bit count equivalent 147 | bits = [int(digit) for digit in bin(val)[2:]] 148 | # now pad the front end with zeros as needed 149 | padCount = numBits - len(bits) 150 | bits = padCount*[0] + bits 151 | return bits 152 | 153 | 154 | # returns a list of bits corresponding to an input list of bytes 155 | def byteListToBits(byteList): 156 | bitList = [] 157 | for byte in byteList: 158 | bitList += decToPaddedBits(byte, 8) 159 | return bitList 160 | 161 | 162 | ###################################################################### 163 | # The following functions handle string conversion of bit lists for 164 | # cleaner input and output. 165 | 166 | # produces a compact string representation of the bit list 167 | def bitsToStr(bitList): 168 | outStr = "" 169 | for bit in bitList: 170 | outStr += str(bit) 171 | return outStr 172 | 173 | 174 | # when passed a bit list that is four bits in length, this function 175 | # returns the nibble as a string 176 | def bitsToNibble(bits, reverse = False): 177 | if reverse: 178 | bits = list(reversed(bits)) 179 | nibble = LOGIC_X 180 | if bits == [0, 0, 0, 0]: nibble = '0' 181 | if bits == [0, 0, 0, 1]: nibble = '1' 182 | if bits == [0, 0, 1, 0]: nibble = '2' 183 | if bits == [0, 0, 1, 1]: nibble = '3' 184 | if bits == [0, 1, 0, 0]: nibble = '4' 185 | if bits == [0, 1, 0, 1]: nibble = '5' 186 | if bits == [0, 1, 1, 0]: nibble = '6' 187 | if bits == [0, 1, 1, 1]: nibble = '7' 188 | if bits == [1, 0, 0, 0]: nibble = '8' 189 | if bits == [1, 0, 0, 1]: nibble = '9' 190 | if bits == [1, 0, 1, 0]: nibble = 'A' 191 | if bits == [1, 0, 1, 1]: nibble = 'B' 192 | if bits == [1, 1, 0, 0]: nibble = 'C' 193 | if bits == [1, 1, 0, 1]: nibble = 'D' 194 | if bits == [1, 1, 1, 0]: nibble = 'E' 195 | if bits == [1, 1, 1, 1]: nibble = 'F' 196 | return nibble 197 | 198 | 199 | # converts list of input bits to a list of bytes 200 | def bit_list_to_byte_list(bits): 201 | if len(bits) % 8 != 0: 202 | print("WARNING: incomplete byte detected in input to bit_list_to_byte_list") 203 | byte_list = [] 204 | for i in xrange(0, len(bits), 8): 205 | bits_in_byte = bits[i:i+8] 206 | byte = bitsToDec(bits_in_byte) 207 | byte_list.append(byte) 208 | return byte_list 209 | 210 | 211 | # when passed a bit list that is eight bits in length, this function 212 | # returns a string representation 213 | def bitsToHexByteString(bits): 214 | if len(bits) == 8: 215 | return bitsToNibble(bits[0:4]) + bitsToNibble(bits[4:8]) 216 | else: 217 | return LOGIC_X + LOGIC_X 218 | 219 | 220 | # takes an input character and returns a bit list 221 | def nibbleToBits(inputNib): 222 | bits = [LOGIC_X, LOGIC_X, LOGIC_X, LOGIC_X] 223 | if inputNib == '0': bits = [0, 0, 0, 0] 224 | if inputNib == '1': bits = [0, 0, 0, 1] 225 | if inputNib == '2': bits = [0, 0, 1, 0] 226 | if inputNib == '3': bits = [0, 0, 1, 1] 227 | if inputNib == '4': bits = [0, 1, 0, 0] 228 | if inputNib == '5': bits = [0, 1, 0, 1] 229 | if inputNib == '6': bits = [0, 1, 1, 0] 230 | if inputNib == '7': bits = [0, 1, 1, 1] 231 | if inputNib == '8': bits = [1, 0, 0, 0] 232 | if inputNib == '9': bits = [1, 0, 0, 1] 233 | if inputNib == 'A': bits = [1, 0, 1, 0] 234 | if inputNib == 'B': bits = [1, 0, 1, 1] 235 | if inputNib == 'C': bits = [1, 1, 0, 0] 236 | if inputNib == 'D': bits = [1, 1, 0, 1] 237 | if inputNib == 'E': bits = [1, 1, 1, 0] 238 | if inputNib == 'F': bits = [1, 1, 1, 1] 239 | return bits 240 | 241 | 242 | # takes an input string of length 2 and returns a bit list 243 | def hexByteToBits(inputHexByteString): 244 | if inputHexByteString == "0": 245 | hexBits = nibbleToBits("0") + nibbleToBits("0") 246 | else: 247 | hexBits = nibbleToBits(inputHexByteString[0]) \ 248 | + nibbleToBits(inputHexByteString[1]) 249 | return hexBits 250 | 251 | 252 | # takes an input character and returns the inverted nibble 253 | def nibbleInvert(inputNib): 254 | nib = LOGIC_X 255 | if inputNib == '0': nib = 'F' 256 | if inputNib == '1': nib = 'E' 257 | if inputNib == '2': nib = 'D' 258 | if inputNib == '3': nib = 'C' 259 | if inputNib == '4': nib = 'B' 260 | if inputNib == '5': nib = 'A' 261 | if inputNib == '6': nib = '9' 262 | if inputNib == '7': nib = '8' 263 | if inputNib == '8': nib = '7' 264 | if inputNib == '9': nib = '6' 265 | if inputNib == 'A': nib = '5' 266 | if inputNib == 'B': nib = '4' 267 | if inputNib == 'C': nib = '3' 268 | if inputNib == 'D': nib = '2' 269 | if inputNib == 'E': nib = '1' 270 | if inputNib == 'F': nib = '0' 271 | return nib 272 | 273 | 274 | # takes an input hex byte string of length two and returns a bit list 275 | def hexStringToDec(inputHexByteString, reverse = False): 276 | return bitsToDec(hexByteToBits(inputHexByteString), reverse) 277 | 278 | 279 | # takes an input hex string, two bytes in length, and returns a bit list 280 | def hexShortToDec(byteLowString, byteHighString, reverse = False): 281 | decimalLow = hexToDec(byteLowString, reverse) 282 | decimalHigh = hexToDec(byteHighString, reverse) 283 | decimalWord = 256*256*decimalHigh + decimalLow 284 | return decimalWord 285 | 286 | # prints a list of byte values as ASCII 287 | def print_bytes_as_ascii(byte_list): 288 | for byte in byte_list: 289 | sys.stdout.write(chr(byte)) 290 | print("") 291 | -------------------------------------------------------------------------------- /build_baseband.py: -------------------------------------------------------------------------------- 1 | import json 2 | from collections import OrderedDict 3 | from pprint import pprint 4 | 5 | def toggleLevel(level): 6 | if level == 0: 7 | level = 1 8 | else: 9 | level = 0 10 | return level 11 | 12 | def nibbleToBits(inputNib): 13 | if inputNib == '0': bits = [0, 0, 0, 0] 14 | if inputNib == '1': bits = [0, 0, 0, 1] 15 | if inputNib == '2': bits = [0, 0, 1, 0] 16 | if inputNib == '3': bits = [0, 0, 1, 1] 17 | if inputNib == '4': bits = [0, 1, 0, 0] 18 | if inputNib == '5': bits = [0, 1, 0, 1] 19 | if inputNib == '6': bits = [0, 1, 1, 0] 20 | if inputNib == '7': bits = [0, 1, 1, 1] 21 | if inputNib == '8': bits = [1, 0, 0, 0] 22 | if inputNib == '9': bits = [1, 0, 0, 1] 23 | if inputNib == 'A': bits = [1, 0, 1, 0] 24 | if inputNib == 'B': bits = [1, 0, 1, 1] 25 | if inputNib == 'C': bits = [1, 1, 0, 0] 26 | if inputNib == 'D': bits = [1, 1, 0, 1] 27 | if inputNib == 'E': bits = [1, 1, 1, 0] 28 | if inputNib == 'F': bits = [1, 1, 1, 1] 29 | return bits 30 | 31 | def populateLevel(outFile, level, sampleCount): 32 | for _ in xrange(sampleCount): 33 | if level == 1: 34 | outFile.write(b'\x01') 35 | else: 36 | outFile.write(b'\x00') 37 | 38 | 39 | 40 | class basebandDefinition: 41 | initialVal = 0 42 | elementList = [] 43 | widthList = [] 44 | waveList = [] 45 | 46 | def __init__(self): 47 | print "Creating new baseband definition object" 48 | 49 | def readFromFile(self, inFileName): 50 | # read each line of the file into a list item 51 | with open(inFileName) as inFile: 52 | lines = inFile.read().splitlines() 53 | 54 | # split each line into list items 55 | for line in lines: 56 | # chop off anything at a '#' char and after 57 | decommentLine = line.split('#')[0] 58 | # ignore any empty lines 59 | if len(decommentLine) > 0: 60 | # create list for this line 61 | lineList = decommentLine.split() 62 | 63 | if lineList[0] == "INIT": 64 | initialVal = int(lineList[1]) 65 | elif lineList[0] == "REG": 66 | self.elementList.append(lineList) 67 | elif lineList[0] == "NRZ": 68 | self.elementList.append(lineList) 69 | elif lineList[0] == "ARB": 70 | self.elementList.append(lineList) 71 | # add manchester and PWM 72 | 73 | def buildWidthList(self): 74 | level = self.initialVal 75 | self.widthList = [] 76 | for element in self.elementList: 77 | if element[0] == "ARB": 78 | level = int(element[1]) 79 | for width in element[2:]: 80 | self.widthList.append([level, int(width)]) 81 | level = toggleLevel(level) 82 | if element[0] == "NRZ": 83 | lowWidth = int(element[1]) 84 | highWidth = int(element[2]) 85 | if element[3] == "HEX": 86 | bitList = [] 87 | for byte in element[4:]: 88 | bitList += nibbleToBits(byte[0]) + nibbleToBits(byte[1]) 89 | for bit in bitList: 90 | if bit == 0: 91 | self.widthList.append([0, lowWidth]) 92 | else: 93 | self.widthList.append([1, highWidth]) 94 | elif element[3] == "ASCII": 95 | bitList = [] 96 | for char in element[5]: 97 | asciiVal = ord(char) # number from 0 to 255 98 | bitList = [int(n) for n in bin(asciiVal)[2:].zfill(8)] 99 | print str(asciiVal) + " = ", 100 | print bitList 101 | if element[4] == "LSB": # need to reverse for LSB 102 | bitList = bitList[::-1] 103 | for bit in bitList: 104 | if bit == 0: 105 | self.widthList.append([0, lowWidth]) 106 | else: 107 | self.widthList.append([1, highWidth]) 108 | 109 | if element[0] == "REG": 110 | level = int(element[1]) 111 | lowWidth = int(element[2]) 112 | highWidth = int(element[3]) 113 | pulseCount = int(element[4]) 114 | for i in xrange(2*pulseCount): 115 | if level == 0: 116 | self.widthList.append([0, lowWidth]) 117 | else: 118 | self.widthList.append([1, highWidth]) 119 | level = toggleLevel(level) 120 | 121 | return [] 122 | 123 | def buildWave(self, sampleRate): 124 | self.waveList = [] 125 | for pair in self.widthList: 126 | for _ in xrange( int((1.0*sampleRate/1000000) * pair[1]) ): 127 | self.waveList.append(pair[0]) 128 | 129 | def writeWaveToFile(self, outFileName, repeatVal): 130 | with open(outFileName, 'wb') as outFile: 131 | for _ in xrange(repeatVal): 132 | for bit in self.waveList: 133 | if bit == 1: 134 | outFile.write(b'\x01') 135 | else: 136 | outFile.write(b'\x00') 137 | 138 | 139 | def printDefinition(self): 140 | print self.elementList 141 | 142 | def printWidths(self): 143 | for pair in self.widthList: 144 | print pair 145 | 146 | def printWave(self): 147 | for bit in self.waveList: 148 | print bit, 149 | 150 | 151 | def microsecondsToSamples(time, sampleRate): 152 | return int(1.0*time*sampleRate) 153 | 154 | def addSamples(basebandList, signalLevel, arbWidth, sampleRate): 155 | newBasebandList = basebandList 156 | valsToAdd = microsecondsToSamples(arbWidth, sampleRate) 157 | for _ in xrange(valsToAdd): 158 | newBasebandList.append(signalLevel) 159 | if signalLevel == 1: 160 | newSignalLevel = 0 161 | else: 162 | newSignalLevel = 1 163 | return (newBasebandList, newSignalLevel) 164 | 165 | def buildBaseband(jsonFileName, basebandSampleRate, initialValue): 166 | signalLevel = initialValue 167 | 168 | # read from file (move to top level) 169 | print "reading json data from file: " + jsonFileName 170 | with open(jsonFileName) as jsonFile: 171 | #with open("../input_files/test.json") as jsonFile: 172 | jsonObj = json.load(jsonFile, object_pairs_hook=OrderedDict) 173 | print(odString(jsonObj)) 174 | pprint(jsonObj) 175 | 176 | print "building baseband" 177 | basebandList = [] 178 | # start with preamble 179 | preambleJson = jsonObj["preamble"] 180 | pprint(preambleJson) 181 | for element in preambleJson: 182 | if element[0] == "arbitrary": 183 | print "adding arbitrary timing" 184 | for arbWidth in element[1]: 185 | (basebandList, signalLevel) = addSamples(basebandList, signalLevel, arbWidth) 186 | 187 | 188 | 189 | return basebandList 190 | -------------------------------------------------------------------------------- /crc_custom.py: -------------------------------------------------------------------------------- 1 | 2 | import bit_list_utilities as blu 3 | import itertools 4 | 5 | # CRC input bit order 6 | CRC_NORM = 0 # data bits are processed from MSB to LSB 7 | CRC_REVERSE = 1 # data bits are processed from LSB to MSB 8 | CRC_REFLECT = 2 # data processed from MSByte to LSByte, but each byte reversed 9 | CRC_BIT_ORDER_OPTIONS = [CRC_NORM, CRC_REVERSE, CRC_REFLECT] 10 | 11 | # initial value options 12 | CRC_INIT_ZERO = 0 13 | CRC_INIT_ONE = 1 14 | CRC_INIT_OPTIONS = [CRC_INIT_ZERO, CRC_INIT_ONE] 15 | 16 | # output reversal options 17 | CRC_REVERSE_FALSE = 0 18 | CRC_REVERSE_TRUE = 1 19 | CRC_REVERSE_BYTES = 2 20 | CRC_REVERSE_FINAL_OPTIONS = [CRC_REVERSE_FALSE, CRC_REVERSE_TRUE, CRC_REVERSE_BYTES] 21 | 22 | # different options to pad the packet 23 | CRC_NOPAD = 0 24 | CRC_PAD_TO_EVEN = 1 # pad packet to an even multiple of the pad count 25 | CRC_PAD_ABS = 2 # pad packet with pad count worth of bits 26 | CRC_PAD_OPTIONS = [CRC_NOPAD, CRC_PAD_TO_EVEN, CRC_PAD_ABS] 27 | 28 | MASTER_CRC_OPTIONS = list(itertools.product(CRC_BIT_ORDER_OPTIONS, 29 | CRC_INIT_OPTIONS, 30 | CRC_REVERSE_FINAL_OPTIONS)) 31 | 32 | # initialize master poly list, which contains a list of common 33 | # polynomials indexed by length 34 | MASTER_POLY_LIST = [list([]) for _ in xrange(65)] 35 | 36 | # Common CRC Polynomials 37 | POLY_3_GSM = [1,0,1,1] 38 | MASTER_POLY_LIST[3] = [POLY_3_GSM] 39 | 40 | POLY_4_ITU = [1, 0,0,1,1] 41 | MASTER_POLY_LIST[4] = [POLY_4_ITU] 42 | 43 | POLY_5_EPC = [1,0, 1,0,0,1] 44 | POLY_5_ITU = [1,1, 0,1,0,1] 45 | POLY_5_USB = [1,0, 0,1,0,1] 46 | MASTER_POLY_LIST[5] = [POLY_5_EPC, POLY_5_ITU, POLY_5_USB] 47 | 48 | POLY_6_CDMA2000_A = [1,1,0, 0,1,1,1] 49 | POLY_6_CDMA2000_B = [1,0,0, 0,1,1,1] 50 | POLY_6_DARC = [1,0,1, 1,0,0,1] 51 | POLY_6_GSM = [1,1,0, 1,1,1,1] 52 | POLY_6_ITU = [1,0,0, 0,0,1,1] 53 | MASTER_POLY_LIST[6] = [POLY_6_CDMA2000_A, POLY_6_CDMA2000_B, POLY_6_DARC, 54 | POLY_6_GSM, POLY_6_ITU] 55 | 56 | POLY_7_MMC_SD = [1,0,0,0, 1,0,0,1] 57 | POLY_7_MVB = [1,1,1,0, 0,1,0,1] 58 | MASTER_POLY_LIST[7] = [POLY_7_MMC_SD, POLY_7_MVB] 59 | 60 | POLY_8_DVB = [1, 1,1,0,1, 0,1,0,1] 61 | POLY_8_AUTOSAR = [1, 0,0,1,0, 1,1,1,1] 62 | POLY_8_BLUETOOTH = [1, 1,0,1,0, 0,1,1,1] 63 | POLY_8_CCITT = [1, 0,0,0,0, 0,1,1,1] 64 | POLY_8_MAXIM = [1, 0,0,1,1, 0,0,0,1] # used for 1-wire 65 | POLY_8_DARC = [1, 0,0,1,1, 1,0,0,1] 66 | POLY_8_GSM_B = [1, 0,1,0,0, 1,0,0,1] 67 | POLY_8_SAE_J1850 = [1, 0,0,0,1, 1,1,0,1] 68 | POLY_8_WCDMA = [1, 1,0,0,1, 1,0,1,1] 69 | MASTER_POLY_LIST[8] = [POLY_8_DVB, POLY_8_AUTOSAR, POLY_8_BLUETOOTH, 70 | POLY_8_CCITT, POLY_8_MAXIM, POLY_8_DARC, 71 | POLY_8_GSM_B, POLY_8_SAE_J1850, POLY_8_WCDMA] 72 | 73 | POLY_10_ATM = [1,1,0, 0,0,1,1, 0,0,1,1] 74 | POLY_10_CDMA2000 = [1,1,1, 1,1,0,1, 1,0,0,1] 75 | POLY_10_GSM = [1,0,1, 0,1,1,1, 0,1,0,1] 76 | MASTER_POLY_LIST[10] = [POLY_10_ATM, POLY_10_CDMA2000, POLY_10_GSM] 77 | 78 | POLY_11_FLEXRAY = [1,0,1,1, 1,0,0,0, 0,1,0,1] 79 | MASTER_POLY_LIST[11] = [POLY_11_FLEXRAY] 80 | 81 | POLY_12 = [1, 1,0,0,0, 0,0,0,0, 1,1,1,1] 82 | POLY_12_CDMA2000 = [1, 1,1,1,1, 0,0,0,1, 0,0,1,1] 83 | POLY_12_GSM = [1, 1,1,0,1, 0,0,1,1, 0,0,0,1] 84 | MASTER_POLY_LIST[12] = [POLY_12, POLY_12_CDMA2000, POLY_12_GSM] 85 | 86 | POLY_13_BBC = [1,1, 1,1,0,0, 1,1,1,1, 0,1,0,1] 87 | MASTER_POLY_LIST[13] = [POLY_13_BBC] 88 | 89 | POLY_14_DARC = [1,0,0, 1,0,0,0, 0,0,0,0, 0,1,0,1] 90 | POLY_14_GSM = [1,1,0, 0,0,0,0, 0,0,1,0, 1,1,0,1] 91 | MASTER_POLY_LIST[14] = [POLY_14_DARC, POLY_14_GSM] 92 | 93 | POLY_15_CAN = [1,1,0,0, 0,1,0,1, 1,0,0,1, 1,0,0,1] 94 | POLY_15_MPT1327 = [1,1,1,0, 1,0,0,0, 0,0,0,1, 0,1,0,1] 95 | MASTER_POLY_LIST[15] = [POLY_15_CAN, POLY_15_MPT1327] 96 | 97 | POLY_16_CHAKRAVARTY = [1, 0,0,1,0, 1,1,1,1, 0,0,0,1, 0,1,0,1] 98 | POLY_16_ARINC = [1, 1,0,1,0, 0,0,0,0, 0,0,1,0, 1,0,1,1] 99 | POLY_16_CCITT = [1, 0,0,0,1, 0,0,0,0, 0,0,1,0, 0,0,0,1] 100 | POLY_16_CDMA2000 = [1, 1,1,0,0, 1,0,0,0, 0,1,1,0, 0,1,1,1] 101 | POLY_16_DECT = [1, 0,0,0,0, 0,1,0,1, 1,0,0,0, 1,0,0,1] 102 | POLY_16_T10_DIF = [1, 1,0,0,0, 1,0,1,1, 1,0,1,1, 0,1,1,1] 103 | POLY_16_DNP = [1, 0,0,1,1, 1,1,0,1, 0,1,1,0, 0,1,0,1] 104 | POLY_16_IBM = [1, 1,0,0,0, 0,0,0,0, 0,0,0,0, 0,1,0,1] 105 | POLY_16_OPEN_SAFETY_A = [1, 0,1,0,1, 1,0,0,1, 0,0,1,1, 0,1,0,1] 106 | POLY_16_OPEN_SAFETY_B = [1, 0,1,1,1, 0,1,0,1, 0,1,0,1, 1,0,1,1] 107 | POLY_16_PROFIBUS = [1, 0,0,0,1, 1,1,0,1, 1,1,0,0, 1,1,1,1] 108 | MASTER_POLY_LIST[16] = [POLY_16_CHAKRAVARTY, POLY_16_ARINC, POLY_16_CCITT, 109 | POLY_16_CDMA2000, POLY_16_DECT, POLY_16_T10_DIF, 110 | POLY_16_DNP, POLY_16_IBM, POLY_16_OPEN_SAFETY_A, 111 | POLY_16_OPEN_SAFETY_B, POLY_16_PROFIBUS] 112 | 113 | POLY_17_CAN = [1,1, 0,1,1,0, 1,0,0,0, 0,1,0,1, 1,0,1,1] 114 | MASTER_POLY_LIST[17] = [POLY_17_CAN] 115 | 116 | POLY_21_CAN = [1,1, 0,0,0,0, 0,0,1,0, 1,0,0,0, 1,0,0,1, 1,0,0,1] 117 | MASTER_POLY_LIST[21] = [POLY_21_CAN] 118 | 119 | POLY_24_FLEXRAY = [1, 0,1,0,1, 1,1,0,1, 0,1,1,0, 1,1,0,1, 1,1,0,0, 1,0,1,1] 120 | POLY_24_RADIX64 = [1, 1,0,0,0, 0,1,1,0, 0,1,0,0, 1,1,0,0, 1,1,1,1, 1,0,1,1] 121 | MASTER_POLY_LIST[24] = [POLY_24_FLEXRAY, POLY_24_RADIX64] 122 | 123 | POLY_30_CDMA = [1,1,0, 0,0,0,0, 0,0,1,1, 0,0,0,0, 1,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,1] 124 | MASTER_POLY_LIST[30] = [POLY_30_CDMA] 125 | 126 | POLY_32 = [1, 0,0,0,0, 0,1,0,0, 1,1,0,0, 0,0,0,1, 0,0,0,1, 1,1,0,1, 1,0,1,1, 0,1,1,1] 127 | POLY_32_C = [1, 0,0,0,1, 1,1,1,0, 1,1,0,1, 1,1,0,0, 0,1,1,0, 1,1,1,1, 0,1,0,0, 0,0,0,1] 128 | POLY_32_K = [1, 0,1,1,1, 0,1,0,0, 0,0,0,1, 1,0,1,1, 1,0,0,0, 1,1,0,0, 1,1,0,1, 0,1,1,1] 129 | POLY_32_K2 = [1, 0,0,1,1, 0,0,1,0, 0,1,0,1, 1,0,0,0, 0,0,1,1, 0,1,0,0, 1,0,0,1, 1,0,0,1] 130 | POLY_32_Q = [1, 1,0,0,0, 0,0,0,1, 0,1,0,0, 0,0,0,1, 0,1,0,0, 0,0,0,1, 1,0,1,0, 1,0,1,1] 131 | MASTER_POLY_LIST[32] = [POLY_32, POLY_32_C, POLY_32_K, POLY_32_K2, 132 | POLY_32_Q] 133 | 134 | POLY_40_GSM = [1] + list(blu.decToPaddedBits(intVal=0x0004820009, numBits=40)) 135 | MASTER_POLY_LIST[40] = [POLY_40_GSM] 136 | 137 | POLY_64_ECMA = [1] + blu.decToPaddedBits(intVal=0x42F0E1EBA9EA3693, numBits=64) 138 | POLY_64_ISO = [1] + blu.decToPaddedBits(intVal=0x000000000000001B, numBits=64) 139 | MASTER_POLY_LIST[64] = [POLY_64_ECMA, POLY_64_ISO] 140 | 141 | # This class defines all of the parameters of a CRC, and is easier to pass 142 | # into and out of functions than a set of variables. It is also possible to 143 | # assign lists to each of the variables and use as an input to the 144 | # brute force crc discovery functions 145 | class CrcDefinition: 146 | crcLen = 2 147 | crcPoly = [1, 0, 1] 148 | crcStart = 0 149 | crcStop = 0 150 | dataStart = 0 151 | dataStop = 0 152 | inputBitOrder = CRC_NORM 153 | initVal = 0 154 | reverseFinal = False 155 | finalXOR = [0, 0] 156 | padType = CRC_NOPAD 157 | padCount = 0 158 | padVal = 0 159 | 160 | def __init__(self, crcLen, crcPoly, dataStart, dataStop, 161 | inputBitOrder, initVal, reverseFinal, 162 | finalXOR, padType, padCount, padVal, 163 | crcStart = 0, crcStop = 0): 164 | self.crcLen = crcLen 165 | self.crcPoly = crcPoly 166 | self.dataStart = dataStart 167 | self.dataStop = dataStop 168 | self.inputBitOrder = inputBitOrder 169 | self.initVal = initVal 170 | self.reverseFinal = reverseFinal 171 | self.finalXOR = finalXOR 172 | self.padType = padType 173 | self.padCount = padCount 174 | self.padVal = padVal 175 | self.crcStart = crcStart 176 | self.crcStop = crcStop 177 | 178 | 179 | # computes CRC of the specified dataStart...dataStop range using 180 | # the CRC definition; use this when you want to generate a CRC 181 | def computeCRC(self, inputData): 182 | return crcCompute(payload=inputData[self.dataStart:self.dataStop+1], 183 | crcPoly=self.crcPoly, 184 | inputBitOrder=self.inputBitOrder, 185 | initVal=self.initVal, 186 | reverseFinal=self.reverseFinal, 187 | finalXOR=self.finalXOR, 188 | padType=self.padType, 189 | padCount=self.padCount, 190 | padVal=self.padVal) 191 | 192 | 193 | # if your data already contains a CRC and you want to check it, 194 | # make sure the crcStart...crcStop indices are defined and 195 | # run this function 196 | def checkCRC(self, inputData): 197 | crcObserved = inputData[self.crcStart:self.crcStop+1] 198 | crcComputed = crcCompute(payload=inputData[self.dataStart:self.dataStop+1], 199 | crcPoly=self.crcPoly, 200 | inputBitOrder=self.inputBitOrder, 201 | initVal=self.initVal, 202 | reverseFinal=self.reverseFinal, 203 | finalXOR=self.finalXOR, 204 | padType=self.padType, 205 | padCount=self.padCount, 206 | padVal=self.padVal) 207 | return crcObserved == crcComputed 208 | 209 | # generates a string containing the CRC properties stored in the object 210 | def crcPropertiesString(self): 211 | str = "CRC Properties:\n" 212 | str += " Length = {}\n".format(self.crcLen) 213 | str += " Poly = {}\n".format(self.crcPoly) 214 | str += " Input Bit Order = {}\n".format(self.inputBitOrder) 215 | str += " Initial Value = {}\n".format(self.initVal) 216 | str += " Reverse Final = {}\n".format(self.reverseFinal) 217 | str += " Final XOR = {}\n".format(self.finalXOR) 218 | str += " Pad Type = {}\n".format(self.padType) 219 | str += " Pad Count = {}\n".format(self.padCount) 220 | str += " Pad Value = {}\n".format(self.padVal) 221 | str += " Indices:\n" 222 | str += " CRC Start = {:3}\n".format(self.crcStart) 223 | str += " CRC Stop = {:3}\n".format(self.crcStop) 224 | str += " Data Start = {:3}\n".format(self.dataStart) 225 | str += " Data Stop = {:3}\n".format(self.dataStop) 226 | return str 227 | 228 | 229 | # if you have set the variables in this object to lists of possible values, 230 | # this function then iterates over all of these CRC properies and tries them 231 | # out on the list of input data values; after iterating through all of the 232 | # possibilities, it returns the most successful options 233 | def crcIterate(self, inputDataList, verbose): 234 | solutionSpace = list(itertools.product(MASTER_POLY_LIST[self.crcLen], 235 | self.inputBitOrder, 236 | self.initVal, 237 | self.reverseFinal, 238 | self.finalXOR, 239 | self.PadType, 240 | self.PadCount, 241 | self.PadVal, 242 | self.crcStart, 243 | self.crcStop, 244 | self.dataStart, 245 | self.dataStop)) 246 | 247 | masterSuccessList = [] 248 | for crcPoly, inputBitOrder, initVal, reverseFinal,\ 249 | finalXOR, padType, PadCount, PadVal,\ 250 | crcStart, crcStop, dataStart, dataStop in solutionSpace: 251 | 252 | successList = [] 253 | for inputData in inputDataList: 254 | crcObserved = inputData[crcStart:crcStop+1] 255 | crcComputed = crcCompute(payload=inputData[dataStart:dataStop+1], 256 | crcPoly=crcPoly, 257 | inputBitOrder=inputBitOrder, 258 | initVal=initVal, 259 | reverseFinal=reverseFinal, 260 | finalXOR=finalXOR, 261 | padType=padType, 262 | padCount=padCount, 263 | padVal=padVal) 264 | successList.append(crcObserved == crcComputed) 265 | 266 | masterSuccessList.append(successList) 267 | 268 | # go through results and determine the most successful attempt 269 | successCount = [] 270 | for successList in masterSuccessList: 271 | successCount.append(sum(int(successList))) 272 | 273 | # this returns the index of the best attempt 274 | indexOfBest = successCount.index(max(successCount)) 275 | 276 | # display the results of the most successful attempts 277 | if verbose: 278 | print("Successes: {} at index = {}".format(max(successCount), indexOfBest)) 279 | print(masterSuccessList) 280 | 281 | # return a CRC object containing the parameters of the most successful 282 | # need to acess the solution space with the index and get the values 283 | print(solutionSpace[indexOfBest]) 284 | return 0 285 | 286 | 287 | class AcsDefinition(): 288 | dataStart = 0, 289 | dataStop = 0, 290 | acsStart = 0, 291 | acsStop = 0, 292 | dataInvert = False, 293 | dataReverse = CRC_REVERSE_FALSE, 294 | numOutputBits = 8, 295 | initSum = 0 296 | 297 | def __init__(self, dataStart, dataStop, dataInvert, dataReverse, numOutputBits, initSum, acsStart, acsStop): 298 | self.dataStart = dataStart 299 | self.dataStop = dataStop 300 | self.dataInvert = dataInvert 301 | self.dataReverse = dataReverse 302 | self.numOutputBits = numOutputBits 303 | self.initSum = initSum 304 | self.acsStart = acsStart 305 | self.acsStop = acsStop 306 | 307 | 308 | def computeACS(self, inputData): 309 | return checksumCompute(dataList=inputData, 310 | dataStart=self.dataStart, 311 | dataStop=self.dataStop, 312 | dataInvert=self.dataInvert, 313 | dataReverse=self.dataReverse, 314 | numOutputBits=self.numOutputBits, 315 | initSum=self.initSum) 316 | 317 | 318 | def checkACS(self, inputData): 319 | acsObserved = inputData[self.acsStart:self.acsStop+1] 320 | acsComputed = checksumCompute(dataList=inputData, 321 | dataStart=self.dataStart, 322 | dataStop=self.dataStop, 323 | dataInvert=self.dataInvert, 324 | dataReverse=self.dataReverse, 325 | numOutputBits=self.numOutputBits, 326 | initSum=self.initSum) 327 | return blu.bitsToDec(acsObserved) == acsComputed 328 | 329 | 330 | # generates a string containing the ACS properties stored in the object 331 | def acsPropertiesString(self): 332 | str = "ACS Properties:\n" 333 | str += " Invert Data = {}\n".format(self.dataInvert) 334 | str += " Reverse Data = {}\n".format(self.dataReverse) 335 | str += " Number of Output Bits = {}\n".format(self.numOutputBits) 336 | str += " Initial Sum = {}\n".format(self.initSum) 337 | str += " Indices:\n" 338 | str += " ACS Start = {:3}\n".format(self.acsStart) 339 | str += " ACS Stop = {:3}\n".format(self.acsStop) 340 | str += " Data Start = {:3}\n".format(self.dataStart) 341 | str += " Data Stop = {:3}\n".format(self.dataStop) 342 | return str 343 | 344 | 345 | def iterateACS(self, inputDataList, verbose): 346 | return 0 347 | 348 | ##################################### 349 | # Note: payload and crcPoly are lists of integers, each valued either 1 or 0 350 | def crcCompute(payload, crcPoly, inputBitOrder, initVal, reverseFinal, 351 | finalXOR, padType, padCount, padVal): 352 | 353 | # print out inputs before proceeding 354 | if False: 355 | print("payload length: {}".format(len(payload))) 356 | print("crcPoly: {}".format(crcPoly)) 357 | print("inputBitOrder: " + str(inputBitOrder)) 358 | print("initVal: " + str(initVal)) 359 | print("reverseFinal: " + str(reverseFinal)) 360 | print("finalXOR: {}".format(finalXOR)) 361 | print("padType: " + str(padType)) 362 | print("padCount: " + str(padCount)) 363 | print("padVal: " + str(padVal)) 364 | 365 | # pad the packet as instructed 366 | payloadPad = payload[:]; 367 | if padType == CRC_PAD_ABS: # add fixed number of bits 368 | for i in range(padCount): 369 | payloadPad.append(padVal) 370 | elif padType == CRC_PAD_TO_EVEN: 371 | if padCount != 0: 372 | numBits = len(payload) % padCount # figure how many short of even 373 | else: 374 | numBits = len(payload) 375 | for i in range(numBits): 376 | payloadPad.append(padVal) 377 | 378 | # reflecting means reversing the bits within each byte 379 | # note, this will only work if the payload is a multiple of 8 long 380 | if inputBitOrder == CRC_REFLECT: 381 | payloadIn = payloadPad[:] 382 | i = 0 383 | while i <= len(payloadIn)-8: 384 | payloadByte = payloadIn[i:i+8] 385 | payloadIn[i:i+8] = payloadByte[::-1] # assign to reversed byte 386 | i += 8 387 | # reverse the payload if instructed 388 | elif inputBitOrder == CRC_REVERSE: 389 | payloadIn = payloadPad[::-1] 390 | # else process normally 391 | else: 392 | payloadIn = payloadPad[:] 393 | 394 | # the working value of the computation is the payload input padded 395 | # by a number of bits equal to one less than the poly length 396 | # these bit positions allow for a remainder of the division 397 | for i in range(len(crcPoly) - 1): 398 | payloadIn.append(initVal) # CRCs can have different initial values 399 | 400 | #print("range i and j and len(payloadIn):") 401 | #print(range(len(payload))) 402 | #print(range(len(crcPoly))) 403 | #print(len(payloadIn) #print payload) 404 | #print(payloadIn) 405 | 406 | for i in range(len(payload)): 407 | if (payloadIn[i] == 1): 408 | for j in range(len(crcPoly)): 409 | payloadIn[i+j] = (payloadIn[i+j]+crcPoly[j]) % 2 410 | #print(payloadIn) 411 | 412 | # crc value is the remainder which is stored in the final bits 413 | # of the computation 414 | crcRemainder = payloadIn[-(len(crcPoly)-1):] 415 | 416 | # final reversal of CRC bits 417 | if reverseFinal == CRC_REVERSE_TRUE: 418 | crcOut = crcRemainder[::-1] 419 | elif reverseFinal == CRC_REVERSE_FALSE: 420 | crcOut = crcRemainder[:] 421 | elif reverseFinal == CRC_REVERSE_BYTES: 422 | crcOut = blu.byteReverse(crcRemainder) 423 | 424 | # final XOR mask 425 | crcXOR = [] 426 | for i in range(len(crcOut)): 427 | if ((crcOut[i]==1) ^ (finalXOR[i]==1)): 428 | crcXOR.append(1) 429 | else: 430 | crcXOR.append(0) 431 | 432 | return(crcXOR) 433 | 434 | 435 | # This function iterates over a series of payloads and associated CRC 436 | # values. Each iteration uses a different set of CRC configurations, as 437 | # supplied in the arguments. Each argument is a list of the values that 438 | # you want to iterate over. If you want to fix a particular configuration 439 | # value, then simply pass a single-element list. 440 | # 441 | # Note: passing an empty list for the crcPoly will cause the function to 442 | # iterate over all common polynomials for that length. 443 | # 444 | # The function returns the set of parameters that has the most success 445 | # matching the observed CRC. 446 | def crcIterate(dataListofLists, 447 | dataStartList, 448 | dataStopList, 449 | crcPolyLen, 450 | crcPolyList, 451 | inputBitOrderList, 452 | initValList, 453 | reverseFinalList, 454 | finalXORList, 455 | padTypeList, 456 | padCountList, 457 | padValList): 458 | return 0 459 | 460 | # This function iterates over a series of payloads and associated arithmetic 461 | # checksum options. Each iteration uses a different set of CRC configurations, 462 | # as supplied in the arguments. Each argument is a list of the values that 463 | # you want to iterate over. If you want to fix a particular configuration 464 | # value, then simply pass a single-element list. 465 | # The function returns the set of parameters that has the most success 466 | # matching the observed checksum. 467 | def checkSumIterate(dataListofLists, 468 | observedChecksumList, 469 | dataStartList, 470 | dataStopList, 471 | dataInvertList, 472 | dataReverseList, 473 | singleByteList, 474 | initSumList): 475 | return 0 476 | 477 | # this function computes a simple arithmetic checksum and returns the 478 | # value in a decimal integer 479 | def checksumCompute(dataList, 480 | dataStart, dataStop, 481 | dataInvert = False, 482 | dataReverse = CRC_REVERSE_FALSE, 483 | numOutputBits = 8, 484 | initSum = 0): 485 | 486 | if (dataStop - dataStart + 1) % 8 != 0: 487 | print("WARNING: Input data contains incomplete byte.") 488 | print(" Length of list should be evenly divisible by 8.") 489 | sum = initSum 490 | for i in xrange(dataStart, dataStop, 8): 491 | bitsInByte = dataList[i:i+8] 492 | if dataReverse == CRC_REVERSE_TRUE: 493 | sum += blu.bitsToDec(bitList=bitsInByte, 494 | invert=dataInvert, 495 | reverse=True) 496 | else: 497 | sum += blu.bitsToDec(bitList=bitsInByte, 498 | invert=dataInvert, 499 | reverse=False) 500 | 501 | modVal = 2**numOutputBits 502 | sum = sum % modVal 503 | if dataReverse == CRC_REVERSE_BYTES: 504 | print("Warning: arithmetic checksum byte swap feature not yet implemented") 505 | return sum 506 | 507 | -------------------------------------------------------------------------------- /kbd_utilities.py: -------------------------------------------------------------------------------- 1 | # this file contains keyboard interface utilities for use in 2 | # other applications 3 | class _Getch: 4 | # Gets a single character from standard input. Does not 5 | # echo to the screen. 6 | def __init__(self): 7 | try: 8 | self.impl = _GetchWindows() 9 | except ImportError: 10 | self.impl = _GetchUnix() 11 | 12 | def __call__(self): return self.impl() 13 | 14 | 15 | class _GetchUnix: 16 | def __init__(self): 17 | import tty, sys 18 | 19 | def __call__(self): 20 | import sys, tty, termios 21 | fd = sys.stdin.fileno() 22 | old_settings = termios.tcgetattr(fd) 23 | try: 24 | tty.setraw(sys.stdin.fileno()) 25 | ch = sys.stdin.read(1) 26 | finally: 27 | termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) 28 | return ch 29 | 30 | 31 | class _GetchWindows: 32 | def __init__(self): 33 | import msvcrt 34 | 35 | def __call__(self): 36 | import msvcrt 37 | return msvcrt.getch() 38 | 39 | 40 | getch = _Getch() 41 | -------------------------------------------------------------------------------- /rf_const.py: -------------------------------------------------------------------------------- 1 | 2 | # hardware select options 3 | HW_SEL_FILE = 0 4 | HW_SEL_HACKRF = 1 5 | HW_SEL_USRP = 2 6 | HW_SEL_LIME = 3 7 | -------------------------------------------------------------------------------- /rf_demod.py: -------------------------------------------------------------------------------- 1 | from gnuradio import analog 2 | from gnuradio import blocks 3 | from gnuradio import eng_notation 4 | from gnuradio import filter 5 | from gnuradio import gr 6 | from gnuradio.eng_option import eng_option 7 | from gnuradio.filter import firdes 8 | from gnuradio import zeromq 9 | from gnuradio import digital 10 | from gnuradio import uhd 11 | import pmt 12 | import math 13 | import osmosdr 14 | import numpy 15 | import rf_mod 16 | from rf_const import HW_SEL_FILE, HW_SEL_HACKRF, HW_SEL_USRP, HW_SEL_LIME 17 | 18 | 19 | 20 | # global constants 21 | class ntsc_fm_demod_flowgraph(gr.top_block): 22 | def __init__(self, verbose, center_freq, freq, 23 | samp_rate, bb_samp_rate, 24 | fm_deviation, channel_width, transition_width, 25 | bb_lpf_cutoff, bb_lpf_transition, 26 | tcp_str, 27 | hw_sel, 28 | fifo_name = "", repeat=True, iq_file_name=""): 29 | 30 | gr.top_block.__init__(self) 31 | 32 | if verbose > 0: 33 | print("\nFlowgraph Properties:") 34 | print(" Center Frequency: {} MHz".format(center_freq/1000000.0)) 35 | print(" Tune Frequency: {} MHz".format(freq/1000000.0)) 36 | print(" IQ Sample Rate (in): {} MHz".format(samp_rate/1000000.0)) 37 | print(" BB Sample Rate (out): {} MHz".format(bb_samp_rate/1000000.0)) 38 | print(" FM Deviation: {} MHz".format(fm_deviation/1000000.0)) 39 | print(" Channel Width: {} MHz".format(channel_width/1000000.0)) 40 | print(" Transition Width: {} MHz".format(transition_width/1000000.0)) 41 | print(" BB LPF cutoff: {} MHz".format(bb_lpf_cutoff/1000000.0)) 42 | print(" BB LPF transition: {} MHz".format(bb_lpf_transition/1000000.0)) 43 | if hw_sel == 0: 44 | print(" SDR: HackRF") 45 | elif hw_sel == 1: 46 | print(" SDR: USRP") 47 | print(" FIFO Name: {}".format(fifo_name)) 48 | print(" Repeat: {}".format(repeat)) 49 | print(" IQ File Name: {}".format(iq_file_name)) 50 | 51 | # start by dumping baseband into a file and viewing in grc 52 | 53 | if verbose > 0: 54 | print("Entering NTSC Demodulator...") 55 | 56 | # variables 57 | self.center_freq = center_freq 58 | self.freq = freq 59 | self.samp_rate = samp_rate 60 | self.bb_samp_rate = bb_samp_rate 61 | self.fm_deviation = fm_deviation 62 | self.channel_width = channel_width 63 | self.transition_width = transition_width 64 | self.bb_lpf_cutoff = bb_lpf_cutoff 65 | self.bb_lpf_transition = bb_lpf_transition 66 | self.repeat = repeat 67 | self.iq_file_name = iq_file_name 68 | self.fifo_name = fifo_name 69 | self.hw_sel = hw_sel 70 | 71 | self.tuning_taps = firdes.low_pass(1, 72 | self.samp_rate, 73 | self.channel_width/2, 74 | self.channel_width/16) 75 | self.lpf_taps = firdes.low_pass(1, 76 | samp_rate, 77 | self.bb_lpf_cutoff, 78 | self.bb_lpf_transition, 79 | firdes.WIN_HAMMING, 80 | 6.76) 81 | 82 | # blocks 83 | 84 | # if we were not passed a file name, use the osmocom source 85 | if self.iq_file_name == "": 86 | if verbose > 0: 87 | print("Using SDR as input...") 88 | 89 | if self.hw_sel == 0: 90 | self.osmosdr_source_0 = osmosdr.source( 91 | args="numchan=" + str(1) + " " + '') 92 | self.osmosdr_source_0.set_sample_rate(samp_rate) 93 | self.osmosdr_source_0.set_center_freq(center_freq, 0) 94 | self.osmosdr_source_0.set_freq_corr(0, 0) 95 | self.osmosdr_source_0.set_dc_offset_mode(0, 0) 96 | self.osmosdr_source_0.set_iq_balance_mode(0, 0) 97 | self.osmosdr_source_0.set_gain_mode(False, 0) 98 | self.osmosdr_source_0.set_gain(10, 0) 99 | self.osmosdr_source_0.set_if_gain(20, 0) 100 | self.osmosdr_source_0.set_bb_gain(20, 0) 101 | self.osmosdr_source_0.set_antenna('', 0) 102 | self.osmosdr_source_0.set_bandwidth(0, 0) 103 | elif self.hw_sel == 1: 104 | self.uhd_usrp_source_0 = uhd.usrp_source( 105 | ",".join(("", "")), 106 | uhd.stream_args( 107 | cpu_format="fc32", 108 | channels=range(1), 109 | ), 110 | ) 111 | self.uhd_usrp_source_0.set_samp_rate(samp_rate) 112 | self.uhd_usrp_source_0.set_center_freq(center_freq, 0) 113 | self.uhd_usrp_source_0.set_gain(20, 0) 114 | self.uhd_usrp_source_0.set_antenna('RX2', 0) 115 | 116 | # otherwise, use a file source with throttle 117 | else: 118 | if verbose > 0: 119 | print("Using {} as input...".format(iq_file_name)) 120 | self.blocks_file_source_0 = blocks.file_source( 121 | gr.sizeof_gr_complex * 1, 122 | iq_file_name, 123 | repeat=repeat) 124 | self.blocks_throttle_0 = blocks.throttle( 125 | gr.sizeof_gr_complex * 1, samp_rate, True) 126 | self.connect( 127 | (self.blocks_file_source_0, 0), 128 | (self.blocks_throttle_0, 0) 129 | ) 130 | 131 | # simple tuner 132 | self.analog_sig_source_x_0 = analog.sig_source_c( 133 | samp_rate, 134 | analog.GR_COS_WAVE, 135 | center_freq - freq, 136 | 1, 137 | 0) 138 | self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) 139 | if iq_file_name == "": 140 | if self.hw_sel == 0: 141 | self.connect( 142 | (self.osmosdr_source_0, 0), 143 | (self.blocks_multiply_xx_0, 0) 144 | ) 145 | elif self.hw_sel == 1: 146 | self.connect( 147 | (self.uhd_usrp_source_0, 0), 148 | (self.blocks_multiply_xx_0, 0) 149 | ) 150 | else: 151 | self.connect( 152 | (self.blocks_throttle_0, 0), 153 | (self.blocks_multiply_xx_0, 0) 154 | ) 155 | self.connect( 156 | (self.analog_sig_source_x_0, 0), 157 | (self.blocks_multiply_xx_0, 1) 158 | ) 159 | 160 | """ 161 | # simple decimator 162 | self.keep = blocks.keep_one_in_n(gr.sizeof_gr_complex*1, 4) 163 | self.connect( 164 | (self.blocks_multiply_xx_0, 0), 165 | (self.keep, 0) 166 | ) 167 | """ 168 | 169 | # demod block 170 | self.analog_quadrature_demod_cf_0_0 = analog.quadrature_demod_cf( 171 | (samp_rate) / (2 * math.pi * self.fm_deviation / 8.0)) 172 | self.connect( 173 | (self.blocks_multiply_xx_0, 0), 174 | #(self.keep, 0), 175 | (self.analog_quadrature_demod_cf_0_0, 0) 176 | ) 177 | 178 | # low pass filter the demod output 179 | self.low_pass_filter_1 = filter.filter.fir_filter_fff( 180 | int(samp_rate/bb_samp_rate), 181 | self.lpf_taps 182 | ) 183 | self.connect( 184 | (self.analog_quadrature_demod_cf_0_0, 0), 185 | (self.low_pass_filter_1, 0) 186 | ) 187 | 188 | # output the baseband to a fifo 189 | if False: 190 | self.blocks_file_sink_0 = blocks.file_sink( 191 | gr.sizeof_float * 1, 192 | fifo_name, 193 | False) 194 | self.blocks_file_sink_0.set_unbuffered(False) 195 | self.connect( 196 | (self.low_pass_filter_1, 0), 197 | (self.blocks_file_sink_0, 0) 198 | ) 199 | 200 | """ 201 | # multiply by a constant and then convert to a short to reduce data 202 | self.multiply_const = blocks.multiply_const_vff((10000, )) # how to scale? 203 | self.float_to_short = blocks.float_to_short(1, 1) 204 | self.connect( 205 | (self.low_pass_filter_1, 0), 206 | (self.multiply_const, 0) 207 | ) 208 | self.connect( 209 | (self.multiply_const, 0), 210 | (self.float_to_short, 0) 211 | ) 212 | self.connect( 213 | (self.float_to_short, 0), 214 | (self.zeromq_push_sink_0, 0) 215 | ) 216 | """ 217 | 218 | # now add the ZMQ block for output to the main program 219 | self.zeromq_push_sink_0 = zeromq.push_sink(gr.sizeof_float, # gr.sizeof_short, 220 | 1, 221 | tcp_str, 222 | 100, 223 | False, 224 | 32768*8)#-1) 225 | 226 | self.connect( 227 | (self.low_pass_filter_1, 0), 228 | (self.zeromq_push_sink_0, 0) 229 | ) 230 | 231 | def update_freq(self, freq): 232 | self.freq = freq 233 | self.analog_sig_source_x_0.set_frequency( 234 | self.center_freq - self.freq) 235 | 236 | def update_center_freq(self, center_freq): 237 | self.center_freq = center_freq 238 | self.analog_sig_source_x_0.set_frequency( 239 | self.center_freq - self.freq) 240 | # can't adjust center frequency of hardware if we're running 241 | # with an IQ file 242 | if self.iq_file_name == "": 243 | if self.hw_sel == 0: 244 | self.osmosdr_source_0.set_center_freq(center_freq, 0) 245 | elif self.hw_sel == 1: 246 | self.uhd_usrp_source_0.set_center_freq(center_freq, 0) 247 | 248 | 249 | def update_lpf_cutoff(self, lpf_cutoff): 250 | self.bb_lpf_cutoff = lpf_cutoff 251 | self.lpf_taps = firdes.low_pass(1, 252 | samp_rate, 253 | self.bb_lpf_cutoff, 254 | self.bb_lpf_transition, 255 | firdes.WIN_HAMMING, 256 | 6.76) 257 | self.low_pass_filter_1.set_taps(self.lpf_taps) 258 | 259 | 260 | 261 | # this flowgraph 262 | class ook_rx_zmq(gr.top_block): 263 | def __init__(self, verbose, 264 | center_freq, 265 | freq, 266 | samp_rate, 267 | threshold, 268 | channel_width, 269 | tcp_str, 270 | hw_sel, 271 | payload_len, 272 | transition_width = 0, 273 | iq_file_name=""): 274 | 275 | gr.top_block.__init__(self) 276 | 277 | if True: #verbose > 0: 278 | print("\nFlowgraph Properties:") 279 | print(" Center Frequency: {} MHz".format(center_freq/1000000.0)) 280 | print(" Tune Frequency: {} MHz".format(freq/1000000.0)) 281 | print(" IQ Sample Rate (in): {} MHz".format(samp_rate/1000000.0)) 282 | print(" Channel Width: {} MHz".format(channel_width/1000000.0)) 283 | print(" Transition Width: {} MHz".format(transition_width/1000000.0)) 284 | print(" Threshold: {}".format(threshold)) 285 | print(" IQ File Name: {}".format(iq_file_name)) 286 | print(" ZMQ TCP ADDR: {}".format(tcp_str)) 287 | print(" HW Sel: {}".format(hw_sel)) 288 | 289 | # start by dumping baseband into a file and viewing in grc 290 | 291 | if verbose > 0: 292 | print("Entering OOK Demodulator...") 293 | 294 | # variables 295 | self.center_freq = center_freq 296 | self.freq = freq 297 | self.threshold = threshold 298 | self.channel_width = channel_width 299 | self.samp_rate = samp_rate 300 | if transition_width == 0: 301 | self.transition_width = channel_width/10 302 | else: 303 | self.transition_width = transition_width 304 | self.taps = firdes.low_pass( 305 | 1, 306 | self.samp_rate, 307 | self.channel_width/2, 308 | self.transition_width) 309 | self.symbol_rate = 10e3 310 | self.samples_per_symbol = \ 311 | int(self.samp_rate/self.symbol_rate) 312 | self.payload_len = payload_len 313 | 314 | # blocks, starting from the input channel filter 315 | self.freq_xlating_fir_filter_xxx_0 = \ 316 | filter.freq_xlating_fir_filter_ccc( 317 | 1, 318 | self.taps, 319 | freq - center_freq, 320 | samp_rate) 321 | if hw_sel == HW_SEL_FILE: 322 | self.blocks_file_source_0 = blocks.file_source( 323 | gr.sizeof_gr_complex * 1, 324 | iq_file_name, 325 | True) 326 | # skip the throttle block, we just want to get done 327 | self.connect( 328 | (self.blocks_file_source_0, 0), 329 | (self.freq_xlating_fir_filter_xxx_0, 0)) 330 | elif hw_sel == HW_SEL_HACKRF: 331 | self.osmosdr_source_0 = osmosdr.source( 332 | args="numchan=" + str(1) + " " + 'hackrf=0') 333 | self.osmosdr_source_0.set_sample_rate(samp_rate) 334 | self.osmosdr_source_0.set_center_freq(center_freq, 0) 335 | self.osmosdr_source_0.set_freq_corr(0, 0) 336 | self.osmosdr_source_0.set_dc_offset_mode(0, 0) 337 | self.osmosdr_source_0.set_iq_balance_mode(0, 0) 338 | self.osmosdr_source_0.set_gain_mode(False, 0) 339 | self.osmosdr_source_0.set_gain(10, 0) 340 | self.osmosdr_source_0.set_if_gain(20, 0) 341 | self.osmosdr_source_0.set_bb_gain(20, 0) 342 | self.osmosdr_source_0.set_antenna('', 0) 343 | self.osmosdr_source_0.set_bandwidth(0, 0) 344 | self.connect( 345 | (self.osmosdr_source_0, 0), 346 | (self.freq_xlating_fir_filter_xxx_0, 0)) 347 | 348 | elif self.hw_sel == HW_SEL_USRP: 349 | self.uhd_usrp_source_0 = uhd.usrp_source( 350 | ",".join(("", "")), 351 | uhd.stream_args( 352 | cpu_format="fc32", 353 | channels=range(1), 354 | ), 355 | ) 356 | self.uhd_usrp_source_0.set_samp_rate(samp_rate) 357 | self.uhd_usrp_source_0.set_center_freq(center_freq, 0) 358 | self.uhd_usrp_source_0.set_gain(20, 0) 359 | self.uhd_usrp_source_0.set_antenna('RX2', 0) 360 | self.connect( 361 | (self.osmosdr_source_0, 0), 362 | (self.uhd_usrp_source_0, 0)) 363 | 364 | # add limeSDR using osmocom 365 | #elif self.hw_sel == HW_SEL_LIME: 366 | 367 | # demodulation 368 | self.blocks_complex_to_mag_0 = blocks.complex_to_mag(1) 369 | self.blocks_add_const_vxx_0 = blocks.add_const_vff( 370 | (-1 * threshold,)) 371 | self.connect( 372 | (self.freq_xlating_fir_filter_xxx_0, 0), 373 | (self.blocks_complex_to_mag_0, 0)) 374 | self.connect( 375 | (self.blocks_complex_to_mag_0, 0), 376 | (self.blocks_add_const_vxx_0, 0)) 377 | 378 | # binary slicer is next major thing, but there 379 | # may be a pfb block in between 380 | self.digital_binary_slicer_fb_0 = digital.binary_slicer_fb() 381 | 382 | # polyphase clock sync (optional) 383 | if False: 384 | nfilts = 32 385 | self.rrc_taps = rrc_taps = \ 386 | firdes.root_raised_cosine( 387 | nfilts, 388 | samp_rate, 389 | self.symbol_rate, 390 | 0.35, 391 | nfilts) 392 | self.digital_pfb_clock_sync_xxx_0_0 = \ 393 | digital.pfb_clock_sync_fff( 394 | self.samples_per_symbol, 395 | 62.8e-3, 396 | (rrc_taps), 397 | 32, 398 | 16, 399 | 1.5, 400 | self.samples_per_symbol) 401 | self.connect( 402 | (self.blocks_add_const_vxx_0, 0), 403 | (self.digital_pfb_clock_sync_xxx_0_0, 0)) 404 | self.connect( 405 | (self.blocks_add_const_vxx_0, 0), 406 | (self.digital_binary_slicer_fb_0, 0) 407 | ) 408 | else: 409 | self.connect( 410 | (self.blocks_add_const_vxx_0, 0), 411 | (self.digital_binary_slicer_fb_0, 0)) 412 | 413 | # decimate down to the symbol rate 414 | self.blocks_keep_one_in_n_0 = blocks.keep_one_in_n( 415 | gr.sizeof_char * 1, 416 | int(self.samp_rate / self.symbol_rate)) 417 | self.connect( 418 | (self.digital_binary_slicer_fb_0, 0), 419 | (self.blocks_keep_one_in_n_0, 0)) 420 | 421 | # find preamble and apply tag 422 | self.digital_correlate_access_code_xx_ts_0 = \ 423 | digital.correlate_access_code_bb_ts( 424 | "01010101010101010101", 425 | 0, 426 | "packet_len") 427 | self.connect( 428 | (self.blocks_keep_one_in_n_0, 0), 429 | (self.digital_correlate_access_code_xx_ts_0, 0)) 430 | 431 | # pack bitstream into bytes 432 | self.blocks_repack_bits_bb_0_0_0 = blocks.repack_bits_bb( 433 | 1, 434 | 8, 435 | 'packet_len', 436 | False, 437 | gr.GR_MSB_FIRST) 438 | self.connect( 439 | (self.digital_correlate_access_code_xx_ts_0, 0), 440 | (self.blocks_repack_bits_bb_0_0_0, 0) 441 | ) 442 | 443 | # generate PDU 444 | self.blocks_tagged_stream_to_pdu_0 = \ 445 | blocks.tagged_stream_to_pdu( 446 | blocks.byte_t, 447 | 'packet_len') 448 | self.connect( 449 | (self.blocks_repack_bits_bb_0_0_0, 0), 450 | (self.blocks_tagged_stream_to_pdu_0, 0)) 451 | 452 | # add ZeroMQ sink to get data out of flowgraph 453 | self.zeromq_push_msg_sink_0 = zeromq.push_msg_sink( 454 | tcp_str, 100) 455 | self.msg_connect( 456 | (self.blocks_tagged_stream_to_pdu_0, 'pdus'), 457 | (self.zeromq_push_msg_sink_0, 'in')) 458 | 459 | # debug only 460 | if False: 461 | self.blocks_random_pdu_0 = blocks.random_pdu( 462 | 50, 128, chr(0xFF), 2) 463 | self.msg_connect( 464 | (self.blocks_random_pdu_0, 'pdus'), 465 | (self.zeromq_push_msg_sink_0, 'in')) 466 | 467 | 468 | 469 | -------------------------------------------------------------------------------- /rf_file_handler.py: -------------------------------------------------------------------------------- 1 | # These functions process and produce names for IQ data files using the 2 | # following format: 3 | # _c
_s.iq 4 | # 5 | # both the center frequency and the sample rate must be expressed in 6 | # with gnuradio (eng_option) suffixes: 7 | # k = 10^3 8 | # M = 10^6 9 | # G = 10^9 10 | # 11 | # Example: a file named keyfob00_c315M_s8M.iq denotes the following: 12 | # center_freq = 315MHz 13 | # sample_rate = 8MHz 14 | # 15 | # If you need to use a decimal point, simply include a "p" character: 16 | # keyfob01_315p1_s8M.iq 17 | 18 | debug = False 19 | 20 | def fileNameTextToFloat(valStr, unitStr): 21 | # if there's a 'p' character, then we have to deal with decimal vals 22 | if 'p' in valStr: 23 | print("decimal value found") 24 | regex = re.compile(r"([0-9]+)p([0-9]+)") 25 | wholeVal = regex.findall(valStr)[0][0] 26 | decimalVal = regex.findall(valStr)[0][1] 27 | baseVal = 1.0*int(wholeVal) + 1.0*int(decimalVal)/10**len(decimalVal) 28 | else: 29 | baseVal = 1.0*int(valStr) 30 | 31 | if unitStr == "G": 32 | multiplier = 1e9 33 | elif unitStr == "M": 34 | multiplier = 1e6 35 | elif unitStr == "k": 36 | multiplier = 1e3 37 | else: 38 | multiplier = 1.0 39 | 40 | return baseVal * multiplier 41 | 42 | 43 | import re 44 | class iqFileObject(): 45 | def __init__(self, prefix = None, centerFreq = None, 46 | sampRate = None, fileName = None): 47 | # if no file name is specified, store the parameters 48 | if fileName is None: 49 | self.prefix = prefix 50 | self.centerFreq = centerFreq 51 | self.sampRate = sampRate 52 | # if the file name is specified, we must derive the parameters 53 | # from the file name 54 | else: 55 | # first check if we have a simple file name or a name+path 56 | regex = re.compile(r"\/") 57 | if regex.match(fileName): 58 | # separate the filename from the rest of the path 59 | regex = re.compile(r"\/([a-zA-Z0-9_.]+)$") 60 | justName = regex.findall(fileName)[0] 61 | else: 62 | justName = fileName 63 | # get the substrings representing the values 64 | regex = re.compile(r"_c([0-9p]+)([GMK])_s([0-9p]+)([GMk])\.iq$") 65 | paramList = regex.findall(justName) 66 | centerValStr = paramList[0][0] 67 | centerUnitStr = paramList[0][1] 68 | sampValStr = paramList[0][2] 69 | sampUnitStr = paramList[0][3] 70 | 71 | if debug: 72 | print(centerValStr) 73 | print(centerUnitStr) 74 | print(sampValStr) 75 | print(sampUnitStr) 76 | 77 | # compute center frequency and sample rate 78 | self.centerFreq = fileNameTextToFloat(centerValStr, centerUnitStr) 79 | self.sampRate = fileNameTextToFloat(sampValStr, sampUnitStr) 80 | 81 | # get the prefix 82 | nonPrefixLen = len("_c" + centerValStr + centerUnitStr +\ 83 | "_s" + sampValStr + sampUnitStr + ".iq") 84 | self.prefix = justName[0:len(justName)-nonPrefixLen] 85 | 86 | if debug: 87 | print(self.centerFreq) 88 | print(self.sampRate) 89 | print(self.prefix) 90 | 91 | def fileName(self): 92 | tempStr = self.prefix 93 | # add center frequency 94 | # first determine if we should use k, M, G or nothing 95 | # then divide by the appropriate unit 96 | if self.centerFreq > 1e9: 97 | unitMag = 'G' 98 | wholeVal = int(1.0*self.centerFreq/1e9) 99 | decimalVal = (1.0*self.centerFreq - 1e9*wholeVal) 100 | decimalVal = int(decimalVal/1e7) 101 | elif self.centerFreq > 1e6: 102 | unitMag = 'M' 103 | wholeVal = int(1.0*self.centerFreq/1e6) 104 | decimalVal = (1.0*self.centerFreq - 1e6*wholeVal) 105 | decimalVal = int(decimalVal/1e4) 106 | elif self.centerFreq > 1e3: 107 | unitMag = 'k' 108 | wholeVal = int(1.0*self.centerFreq/1e3) 109 | decimalVal = (1.0*self.centerFreq - 1e3*wholeVal) 110 | decimalVal = int(decimalVal/1e1) 111 | else: 112 | unitMag = '' 113 | value = int(self.centerFreq) 114 | if decimalVal == 0: 115 | tempStr += "_c{}{}".format(wholeVal, unitMag) 116 | else: 117 | tempStr += "_c{}p{}{}".format(wholeVal, decimalVal, unitMag) 118 | 119 | # do the same thing for the sample rate 120 | if self.sampRate > 1e6: 121 | unitMag = 'M' 122 | wholeVal = int(1.0*self.sampRate/1e6) 123 | decimalVal = (1.0*self.sampRate - 1e6*wholeVal) 124 | decimalVal = int(decimalVal/1e4) 125 | elif self.sampRate > 1e3: 126 | unitMag = 'k' 127 | wholeVal = int(1.0*self.sampRate/1e3) 128 | decimalVal = (1.0*self.sampRate - 1e3*wholeVal) 129 | value = self.sampRate/1e1 130 | else: 131 | unitMag = '' 132 | value = int(self.sampRate) 133 | if decimalVal == 0: 134 | tempStr += "_s{}{}".format(wholeVal, unitMag) 135 | else: 136 | tempStr += "_s{}p{}{}".format(wholeVal, decimalVal, unitMag) 137 | tempStr += ".iq" 138 | return tempStr 139 | 140 | -------------------------------------------------------------------------------- /rf_mod.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This module contains the functions used to demodulate raw RF files 3 | # in I-Q format. These functions use the gnuradio libraries to tune 4 | # to the signal, then filter and demodulate it. 5 | # 6 | # Each class below with the suffix "_flowgraph" contains all of the 7 | # gnuradio blocks required to process the I-Q data to a digital baseband 8 | # waveform. This baseband waveform is output to a file 9 | 10 | from gnuradio import analog 11 | from gnuradio import blocks 12 | from gnuradio import eng_notation 13 | from gnuradio import gr 14 | from gnuradio.eng_option import eng_option 15 | from gnuradio.filter import firdes 16 | from optparse import OptionParser 17 | from gnuradio import digital 18 | from gnuradio import eng_notation 19 | from gnuradio import filter 20 | from gnuradio.filter import firdes 21 | from gnuradio import uhd 22 | import pmt 23 | import math 24 | import osmosdr 25 | import time 26 | import sys 27 | import numpy 28 | 29 | # global constants 30 | MOD_OOK = 0 31 | MOD_FSK = 1 32 | MOD_DPSK = 2 33 | 34 | 35 | 36 | def modulator(verbose, modulation, center_freq, frequency, samp_rate, 37 | ook_gain, fsk_deviation_hz, channel_width, 38 | basebandFileName, basebandSampleRate, repeat, timeBetweenTx): 39 | if verbose: 40 | print("Entering modulator function") 41 | 42 | # select between the major modulation types 43 | if modulation == MOD_OOK: 44 | if verbose: 45 | print("Instantiating OOK flowgraph...") 46 | flowgraphObject = ook_tx_flowgraph(center_freq = center_freq, 47 | freq = frequency, 48 | samp_rate = samp_rate, 49 | gain = ook_gain, 50 | basebandFileName = basebandFileName, 51 | basebandSampleRate = basebandSampleRate, 52 | repeat = repeat 53 | ) 54 | flowgraphObject.run() 55 | elif modulation == MOD_FSK: 56 | if verbose: 57 | print("Instantiating FSK flowgraph...") 58 | flowgraphObject = fsk_tx_flowgraph(center_freq = center_freq, 59 | freq = frequency, 60 | samp_rate = samp_rate, 61 | fsk_deviation_hz = fsk_deviation_hz, 62 | basebandFileName = basebandFileName, 63 | basebandSampleRate = basebandSampleRate, 64 | repeat = repeat 65 | ) 66 | flowgraphObject.run() 67 | elif modulation == MOD_DPSK: 68 | if verbose: 69 | print("Instantiating DPSK flowgraph...") 70 | flowgraphObject = dpsk_tx_flowgraph() 71 | flowgraphObject.run() 72 | 73 | 74 | #### TESTING FLOWGRAPH ONLY 75 | class top_block_simple(gr.top_block): 76 | 77 | def __init__(self): 78 | gr.top_block.__init__(self, "Top Block") 79 | 80 | ################################################## 81 | # Variables 82 | ################################################## 83 | self.samp_rate = samp_rate = 1e3 84 | 85 | ################################################## 86 | # Message Queues 87 | ################################################## 88 | #blocks_message_sink_0_msgq_out = blocks_message_source_0_msgq_in = gr.msg_queue(2) 89 | self.blocks_message_source_0_msgq_in = gr.msg_queue() 90 | 91 | ################################################## 92 | # Blocks 93 | ################################################## 94 | self.blocks_vector_to_streams_0 = blocks.vector_to_streams(gr.sizeof_char*1, 4) 95 | #self.blocks_vector_source_x_0 = blocks.vector_source_b((1, 0, 0, 1), True, 4, []) 96 | self.blocks_uchar_to_float_0_2 = blocks.uchar_to_float() 97 | self.blocks_uchar_to_float_0_1 = blocks.uchar_to_float() 98 | self.blocks_uchar_to_float_0_0 = blocks.uchar_to_float() 99 | self.blocks_uchar_to_float_0 = blocks.uchar_to_float() 100 | self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True) 101 | self.blocks_message_source_0 = blocks.message_source(gr.sizeof_char*4, self.blocks_message_source_0_msgq_in) 102 | #self.blocks_message_source_0.message_port_register_in(pmt.pmt_intern("input_port")) 103 | #self.blocks_message_sink_0 = blocks.message_sink(gr.sizeof_char*4, blocks_message_sink_0_msgq_out, False) 104 | self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_float*1, "simple.float", False) 105 | self.blocks_file_sink_0.set_unbuffered(False) 106 | self.blocks_add_xx_0 = blocks.add_vff(1) 107 | 108 | ################################################## 109 | # Connections 110 | ################################################## 111 | self.connect((self.blocks_add_xx_0, 0), (self.blocks_throttle_0, 0)) 112 | self.connect((self.blocks_message_source_0, 0), (self.blocks_vector_to_streams_0, 0)) 113 | self.connect((self.blocks_throttle_0, 0), (self.blocks_file_sink_0, 0)) 114 | self.connect((self.blocks_uchar_to_float_0, 0), (self.blocks_add_xx_0, 3)) 115 | self.connect((self.blocks_uchar_to_float_0_0, 0), (self.blocks_add_xx_0, 2)) 116 | self.connect((self.blocks_uchar_to_float_0_1, 0), (self.blocks_add_xx_0, 1)) 117 | self.connect((self.blocks_uchar_to_float_0_2, 0), (self.blocks_add_xx_0, 0)) 118 | #self.connect((self.blocks_vector_source_x_0, 0), (self.blocks_message_sink_0, 0)) 119 | self.connect((self.blocks_vector_to_streams_0, 3), (self.blocks_uchar_to_float_0, 0)) 120 | self.connect((self.blocks_vector_to_streams_0, 2), (self.blocks_uchar_to_float_0_0, 0)) 121 | self.connect((self.blocks_vector_to_streams_0, 1), (self.blocks_uchar_to_float_0_1, 0)) 122 | self.connect((self.blocks_vector_to_streams_0, 0), (self.blocks_uchar_to_float_0_2, 0)) 123 | 124 | # this method transfers data from a python list to the 125 | # flowgraph's message queue for transmission 126 | def fill_queue_vector(self, bb_list, hop_select_list): 127 | 128 | # baseband and select values must be the same 129 | if len(bb_list) != len(hop_select_list): 130 | print("Fatal Error: hop select list must be same length as baseband list") 131 | exit(1) 132 | # create a vector entry of the bb bits and corresponding enables 133 | # vector_sample = numpy.array([b'\x00', 0, 0, 0]) 134 | vector_sample = [b'\x00', b'\x00', b'\x00', b'\x00'] 135 | # now we make a u8 vector out of this 136 | vector_pmt = pmt.make_u8vector(4, 95) 137 | 138 | for i in xrange(len(bb_list)): 139 | # assign first vector element to bb value 140 | vector_sample[0] = b'\x01' if bb_list[i] == 1 else b'\x00' 141 | if hop_select_list[i] == 0: 142 | vector_sample[1] = b'\x01' 143 | vector_sample[2] = b'\x00' 144 | vector_sample[3] = b'\x00' 145 | elif hop_select_list[i] == 1: 146 | vector_sample[1] = b'\x00' 147 | vector_sample[2] = b'\x01' 148 | vector_sample[3] = b'\x00' 149 | elif hop_select_list[i] == 2: 150 | vector_sample[1] = b'\x00' 151 | vector_sample[2] = b'\x00' 152 | vector_sample[3] = b'\x01' 153 | else: 154 | print("Fatal Error: hop select out of range; must be 0-2") 155 | exit(1) 156 | 157 | # vector_pmt = pmt.to_pmt(vector_sample) 158 | vector_str = "" 159 | for uchar in vector_sample: 160 | vector_str += uchar 161 | message = gr.message_from_string(vector_str) 162 | self.blocks_message_source_0_msgq_in.insert_tail(message) 163 | 164 | # THIS STUFF IS THE PMT APPROACH 165 | #port = pmt.intern("input_port") 166 | #self.blocks_message_source_0.to_basic_block()._post(port, vector_pmt) 167 | #message = pmt.cons(pmt.PMT_NIL, vector_pmt) 168 | #self.blocks_message_source_0_msgq_in.insert_tail(message) 169 | 170 | # THIS STUFF IS THE MESSAGE QUEUE APPROACH 171 | # vector_str = gr.message_from_string(vector_sample) 172 | #self.source_queue_v.insert_tail(vector_str) 173 | #vector_string = pmt.write_string(vector_pmt) 174 | #print(vector_string) 175 | #message = gr.message_from_string(vector_string) 176 | #self.blocks_message_source_0_msgq_in.insert_tail(message) 177 | 178 | 179 | 180 | 181 | ######### TESTING 2 182 | class top_block(gr.top_block): 183 | def __init__(self): 184 | gr.top_block.__init__(self, "Top Block") 185 | 186 | ################################################## 187 | # Variables 188 | ################################################## 189 | self.fsk_deviation_hz = fsk_deviation_hz = 15e3 190 | self.channel_width = channel_width = fsk_deviation_hz * 1.3 191 | self.t_width = t_width = channel_width / 10 192 | self.samp_rate = samp_rate = 8e6 193 | self.freq2 = freq2 = 433.777e6 194 | self.freq1 = freq1 = 434.138e6 195 | self.freq0 = freq0 = 433.178e6 196 | self.channel_filter_taps = channel_filter_taps = firdes.low_pass(1, samp_rate, channel_width, t_width) 197 | self.center_freq = center_freq = 435e6 198 | self.baseband_samp_rate = baseband_samp_rate = 16e3 199 | 200 | ################################################## 201 | # Message Queues 202 | ################################################## 203 | #blocks_message_sink_0_msgq_out = blocks_message_source_0_msgq_in = gr.msg_queue(2) 204 | self.blocks_message_source_0_msgq_in = gr.msg_queue() 205 | ################################################## 206 | # Blocks 207 | ################################################## 208 | 209 | self.digital_gfsk_mod_0 = digital.gfsk_mod( 210 | samples_per_symbol=int(samp_rate / baseband_samp_rate), 211 | sensitivity=(2 * math.pi * (fsk_deviation_hz / 2)) / samp_rate, 212 | bt=0.35, 213 | verbose=False, 214 | log=False, 215 | ) 216 | self.blocks_vector_to_streams_0 = blocks.vector_to_streams(gr.sizeof_char * 1, 4) 217 | self.blocks_vector_source_x_0 = blocks.vector_source_b((1, 0, 0, 1), True, 4, []) 218 | self.blocks_unpacked_to_packed_xx_0 = blocks.unpacked_to_packed_bb(1, gr.GR_MSB_FIRST) 219 | self.blocks_uchar_to_float_1_0_0 = blocks.uchar_to_float() 220 | self.blocks_uchar_to_float_1_0 = blocks.uchar_to_float() 221 | self.blocks_uchar_to_float_1 = blocks.uchar_to_float() 222 | self.blocks_throttle_0 = blocks.throttle(gr.sizeof_gr_complex * 1, samp_rate, True) 223 | self.blocks_null_sink_0 = blocks.null_sink(gr.sizeof_gr_complex * 1) 224 | self.blocks_multiply_xx_0_0_0_1 = blocks.multiply_vcc(1) 225 | self.blocks_multiply_xx_0_0_0 = blocks.multiply_vcc(1) 226 | self.blocks_multiply_xx_0_0 = blocks.multiply_vcc(1) 227 | self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) 228 | self.blocks_message_source_0 = blocks.message_source(gr.sizeof_char * 4, self.blocks_message_source_0_msgq_in) 229 | #self.blocks_message_sink_0 = blocks.message_sink(gr.sizeof_char * 4, blocks_message_sink_0_msgq_out, False) 230 | self.blocks_float_to_complex_0_1 = blocks.float_to_complex(1) 231 | self.blocks_float_to_complex_0_0 = blocks.float_to_complex(1) 232 | self.blocks_float_to_complex_0 = blocks.float_to_complex(1) 233 | self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_gr_complex * 1, 234 | "test.iq", 235 | False) 236 | self.blocks_file_sink_0.set_unbuffered(False) 237 | self.blocks_add_xx_0 = blocks.add_vcc(1) 238 | self.analog_sig_source_x_0_0_0_1 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq2 - center_freq, 1, 0) 239 | self.analog_sig_source_x_0_0_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq1 - center_freq, 1, 0) 240 | self.analog_sig_source_x_0_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq0 - center_freq, 1, 0) 241 | 242 | ################################################## 243 | # Connections 244 | ################################################## 245 | self.connect((self.analog_sig_source_x_0_0, 0), (self.blocks_multiply_xx_0_0, 0)) 246 | self.connect((self.analog_sig_source_x_0_0_0, 0), (self.blocks_multiply_xx_0_0_0, 0)) 247 | self.connect((self.analog_sig_source_x_0_0_0_1, 0), (self.blocks_multiply_xx_0_0_0_1, 0)) 248 | self.connect((self.blocks_add_xx_0, 0), (self.blocks_multiply_xx_0, 0)) 249 | self.connect((self.blocks_float_to_complex_0, 0), (self.blocks_multiply_xx_0_0, 1)) 250 | self.connect((self.blocks_float_to_complex_0_0, 0), (self.blocks_multiply_xx_0_0_0, 1)) 251 | self.connect((self.blocks_float_to_complex_0_1, 0), (self.blocks_multiply_xx_0_0_0_1, 1)) 252 | self.connect((self.blocks_message_source_0, 0), (self.blocks_vector_to_streams_0, 0)) 253 | self.connect((self.blocks_multiply_xx_0, 0), (self.blocks_throttle_0, 0)) 254 | self.connect((self.blocks_multiply_xx_0_0, 0), (self.blocks_add_xx_0, 0)) 255 | self.connect((self.blocks_multiply_xx_0_0_0, 0), (self.blocks_add_xx_0, 1)) 256 | self.connect((self.blocks_multiply_xx_0_0_0_1, 0), (self.blocks_add_xx_0, 2)) 257 | self.connect((self.blocks_throttle_0, 0), (self.blocks_file_sink_0, 0)) 258 | self.connect((self.blocks_throttle_0, 0), (self.blocks_null_sink_0, 0)) 259 | self.connect((self.blocks_uchar_to_float_1, 0), (self.blocks_float_to_complex_0, 0)) 260 | self.connect((self.blocks_uchar_to_float_1, 0), (self.blocks_float_to_complex_0, 1)) 261 | self.connect((self.blocks_uchar_to_float_1_0, 0), (self.blocks_float_to_complex_0_0, 0)) 262 | self.connect((self.blocks_uchar_to_float_1_0, 0), (self.blocks_float_to_complex_0_0, 1)) 263 | self.connect((self.blocks_uchar_to_float_1_0_0, 0), (self.blocks_float_to_complex_0_1, 0)) 264 | self.connect((self.blocks_uchar_to_float_1_0_0, 0), (self.blocks_float_to_complex_0_1, 1)) 265 | self.connect((self.blocks_unpacked_to_packed_xx_0, 0), (self.digital_gfsk_mod_0, 0)) 266 | #self.connect((self.blocks_vector_source_x_0, 0), (self.blocks_message_sink_0, 0)) 267 | self.connect((self.blocks_vector_to_streams_0, 1), (self.blocks_uchar_to_float_1, 0)) 268 | self.connect((self.blocks_vector_to_streams_0, 2), (self.blocks_uchar_to_float_1_0, 0)) 269 | self.connect((self.blocks_vector_to_streams_0, 3), (self.blocks_uchar_to_float_1_0_0, 0)) 270 | self.connect((self.blocks_vector_to_streams_0, 0), (self.blocks_unpacked_to_packed_xx_0, 0)) 271 | self.connect((self.digital_gfsk_mod_0, 0), (self.blocks_multiply_xx_0, 1)) 272 | 273 | # this method transfers data from a python list to the 274 | # flowgraph's message queue for transmission 275 | def fill_queue_vector(self, bb_list, hop_select_list): 276 | 277 | # baseband and select values must be the same 278 | if len(bb_list) != len(hop_select_list): 279 | print("Fatal Error: hop select list must be same length as baseband list") 280 | exit(1) 281 | # create a vector entry of the bb bits and corresponding enables 282 | # vector_sample = numpy.array([b'\x00', 0, 0, 0]) 283 | vector_sample = [b'\x00', 0, 0, 0] 284 | # now we make a u8 vector out of this 285 | # vector_pmt = pmt.make_u8vector(4, b'\x00') 286 | 287 | for i in xrange(len(bb_list)): 288 | # assign first vector element to bb value 289 | vector_sample[0] = b'\x01' if bb_list[i] == 1 else b'\x00' 290 | if hop_select_list[i] == 0: 291 | vector_sample[1] = b'\x01' 292 | vector_sample[2] = b'\x00' 293 | vector_sample[3] = b'\x00' 294 | elif hop_select_list[i] == 1: 295 | vector_sample[1] = b'\x00' 296 | vector_sample[2] = b'\x01' 297 | vector_sample[3] = b'\x00' 298 | elif hop_select_list[i] == 2: 299 | vector_sample[1] = b'\x00' 300 | vector_sample[2] = b'\x00' 301 | vector_sample[3] = b'\x01' 302 | else: 303 | print("Fatal Error: hop select out of range; must be 0-2") 304 | exit(1) 305 | 306 | # vector_pmt = pmt.to_pmt(vector_sample) 307 | vector_str = "" 308 | for uchar in vector_sample: 309 | vector_str += uchar 310 | message = gr.message_from_string(vector_str) 311 | self.blocks_message_source_0_msgq_in.insert_tail(message) 312 | 313 | # vector_str = gr.message_from_string(vector_sample) 314 | # self.source_queue_v.insert_tail(vector_str) 315 | 316 | 317 | ############################################################## 318 | class fsk_hop_tx_flowgraph(gr.top_block): 319 | def __init__(self, center_freq, samp_rate, gain, fsk_deviation_hz, 320 | baseband_file_name, baseband_samp_rate, 321 | freq_hop_list, verbose = False, 322 | hardware_transmit_enable = True, hw_sel = 0, hw_gain = 0, 323 | iq_file_out = False): 324 | gr.top_block.__init__(self) 325 | 326 | # display the parameters used for transmit 327 | if True: #verbose: 328 | print("Baseband File Name: {}".format(baseband_file_name)) 329 | print("Baseband Sample Rate: {}".format(baseband_samp_rate)) 330 | print("SDR Center Freq: {}".format(center_freq)) 331 | print("SDR Sample Rate: {}".format(samp_rate)) 332 | print("Flowgraph Gain: {}".format(gain)) 333 | print("GFSK deviation: {}".format(fsk_deviation_hz)) 334 | print("Freq 0-2: {}".format(freq_hop_list)) 335 | print("Verbose: {}".format(verbose)) 336 | print("Hardware TX Enable: {}".format(hardware_transmit_enable)) 337 | print("IQ to File: {}".format(iq_file_out)) 338 | 339 | ################################################## 340 | # Variables 341 | ################################################## 342 | self.center_freq = center_freq 343 | self.samp_rate = samp_rate 344 | self.gain = gain 345 | self.fsk_deviation_hz = fsk_deviation_hz 346 | self.baseband_file_name = baseband_file_name 347 | self.baseband_samp_rate = baseband_samp_rate 348 | self.freq_hop_list = freq_hop_list 349 | self.hw_sel = hw_sel 350 | self.hw_gain = hw_gain 351 | 352 | """ 353 | r = gr.enable_realtime_scheduling() 354 | if r == gr.RT_OK: 355 | print("Note: Realtime scheduling enabled") 356 | """ 357 | # self.cutoff_freq = channel_width/2 358 | ################################################## 359 | # Blocks 360 | ################################################## 361 | # replace this with a message source 362 | #self.blocks_file_source_0 = blocks.file_source( 363 | # gr.sizeof_char * 1, 364 | # baseband_file_name, 365 | # repeat) 366 | # message sink is primary method of getting baseband data into 367 | # the flowgraph 368 | #self.source_queue = gr.msg_queue() 369 | #self.blocks_message_source_0 = blocks.message_source( 370 | # gr.sizeof_char*1, 371 | # self.source_queue) 372 | #self.blocks_message_source_0.set_max_output_buffer(1) 373 | 374 | # TESTING FLOWGRAPH ONLY (DELETE WHEN DONE) 375 | #blocks_message_sink_0_msgq_out = blocks_message_source_0_msgq_in = gr.msg_queue(2) 376 | #self.blocks_vector_source_x_0 = blocks.vector_source_b((1, 0, 0, 1), True, 4, []) 377 | #self.blocks_message_sink_0 = blocks.message_sink(gr.sizeof_char * 4, blocks_message_sink_0_msgq_out, False) 378 | #self.connect((self.blocks_vector_source_x_0, 0), (self.blocks_message_sink_0, 0)) 379 | 380 | self.source_queue_v = gr.msg_queue() 381 | #self.source_queue_v = gr.msg_queue(2048) # smaller values cause hang 382 | self.blocks_message_source_0 = blocks.message_source( 383 | gr.sizeof_char*4, 384 | self.source_queue_v) 385 | self.blocks_vector_to_streams = blocks.vector_to_streams(gr.sizeof_char * 1, 4) 386 | self.connect( 387 | (self.blocks_message_source_0, 0), 388 | (self.blocks_vector_to_streams, 0)) 389 | 390 | # blocks and connections for carrier for first hop frequency 391 | self.analog_sig_source_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq_hop_list[0] - center_freq, 1, 0) 392 | self.repeat_0 = blocks.repeat(gr.sizeof_char * 1, int(samp_rate / baseband_samp_rate)) 393 | self.blocks_uchar_to_float_0 = blocks.uchar_to_float() 394 | self.blocks_float_to_complex_0 = blocks.float_to_complex(1) 395 | self.blocks_multiply_0 = blocks.multiply_vcc(1) 396 | self.connect((self.blocks_vector_to_streams, 1), (self.repeat_0, 0)) 397 | self.connect((self.repeat_0, 0), (self.blocks_uchar_to_float_0, 0)) 398 | self.connect((self.blocks_uchar_to_float_0, 0), (self.blocks_float_to_complex_0, 0)) 399 | self.connect((self.blocks_uchar_to_float_0, 0), (self.blocks_float_to_complex_0, 1)) 400 | self.connect((self.blocks_float_to_complex_0, 0), (self.blocks_multiply_0, 1)) 401 | self.connect((self.analog_sig_source_0, 0), (self.blocks_multiply_0, 0)) 402 | 403 | # blocks and connections for carrier for second hop frequency 404 | self.analog_sig_source_1 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq_hop_list[1] - center_freq, 1, 0) 405 | self.repeat_1 = blocks.repeat(gr.sizeof_char*1, int(samp_rate/baseband_samp_rate)) 406 | self.blocks_uchar_to_float_1 = blocks.uchar_to_float() 407 | self.blocks_float_to_complex_1 = blocks.float_to_complex(1) 408 | self.blocks_multiply_1 = blocks.multiply_vcc(1) 409 | self.connect((self.blocks_vector_to_streams, 2), (self.repeat_1, 0)) 410 | self.connect((self.repeat_1, 0), (self.blocks_uchar_to_float_1, 0)) 411 | self.connect((self.blocks_uchar_to_float_1, 0), (self.blocks_float_to_complex_1, 0)) 412 | self.connect((self.blocks_uchar_to_float_1, 0), (self.blocks_float_to_complex_1, 1)) 413 | self.connect((self.blocks_float_to_complex_1, 0), (self.blocks_multiply_1, 1)) 414 | self.connect((self.analog_sig_source_1, 0), (self.blocks_multiply_1, 0)) 415 | 416 | # blocks and connections for carrier for third hop frequency 417 | self.analog_sig_source_2 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq_hop_list[2] - center_freq, 1, 0) 418 | self.repeat_2 = blocks.repeat(gr.sizeof_char * 1, int(samp_rate / baseband_samp_rate)) 419 | self.blocks_uchar_to_float_2 = blocks.uchar_to_float() 420 | self.blocks_float_to_complex_2 = blocks.float_to_complex(1) 421 | self.blocks_multiply_2 = blocks.multiply_vcc(1) 422 | self.connect((self.blocks_vector_to_streams, 3), (self.repeat_2, 0)) 423 | self.connect((self.repeat_2, 0), (self.blocks_uchar_to_float_2, 0)) 424 | self.connect((self.blocks_uchar_to_float_2, 0), (self.blocks_float_to_complex_2, 0)) 425 | self.connect((self.blocks_uchar_to_float_2, 0), (self.blocks_float_to_complex_2, 1)) 426 | self.connect((self.blocks_float_to_complex_2, 0), (self.blocks_multiply_2, 1)) 427 | self.connect((self.analog_sig_source_2, 0), (self.blocks_multiply_2, 0)) 428 | 429 | # now add the three gated carrier together; the selected 430 | # one will pass through 431 | self.blocks_add = blocks.add_vcc(1) 432 | self.connect((self.blocks_multiply_0, 0), (self.blocks_add, 0)) 433 | self.connect((self.blocks_multiply_1, 0), (self.blocks_add, 1)) 434 | self.connect((self.blocks_multiply_2, 0), (self.blocks_add, 2)) 435 | 436 | # all of the baseband data goes to the modulation chain 437 | self.blocks_unpacked_to_packed = blocks.unpacked_to_packed_bb( 438 | 1, 439 | gr.GR_MSB_FIRST) 440 | self.digital_gfsk_mod = digital.gfsk_mod( 441 | samples_per_symbol=int(samp_rate/baseband_samp_rate), 442 | sensitivity=(2 * math.pi * (fsk_deviation_hz / 2)) / samp_rate, 443 | bt=0.35, 444 | verbose=False, 445 | log=False, 446 | ) 447 | self.connect( 448 | (self.blocks_vector_to_streams, 0), 449 | (self.blocks_unpacked_to_packed, 0)) 450 | self.connect( 451 | (self.blocks_unpacked_to_packed, 0), 452 | (self.digital_gfsk_mod, 0)) 453 | 454 | # use the passed-through carrier to tune/modulate the gfsk output 455 | self.blocks_multiply_tune = blocks.multiply_vcc(1) 456 | self.connect( 457 | (self.digital_gfsk_mod, 0), 458 | (self.blocks_multiply_tune, 0)) 459 | self.connect( 460 | (self.blocks_add, 0), 461 | (self.blocks_multiply_tune, 1)) 462 | 463 | 464 | # setup osmocom block for HackRF control 465 | # NEED: add control switch for USRP models 466 | if hardware_transmit_enable: 467 | if self.hw_sel == 0: 468 | self.osmosdr_sink = osmosdr.sink( 469 | args="numchan=" + str(1) + " " + "") 470 | self.osmosdr_sink.set_sample_rate(samp_rate) 471 | self.osmosdr_sink.set_center_freq(center_freq, 0) 472 | self.osmosdr_sink.set_freq_corr(0, 0) 473 | self.osmosdr_sink.set_gain(hw_gain, 0) 474 | self.osmosdr_sink.set_if_gain(20, 0) 475 | self.osmosdr_sink.set_bb_gain(20, 0) 476 | self.osmosdr_sink.set_antenna("", 0) 477 | self.osmosdr_sink.set_bandwidth(0, 0) 478 | self.connect( 479 | (self.blocks_multiply_tune, 0), 480 | (self.osmosdr_sink, 0)) 481 | elif self.hw_sel == 1: 482 | self.uhd_usrp_sink_0 = uhd.usrp_sink( 483 | ",".join(("", "")), 484 | uhd.stream_args( 485 | cpu_format="fc32", 486 | channels=range(1), 487 | ), 488 | ) 489 | self.uhd_usrp_sink_0.set_samp_rate(samp_rate) 490 | self.uhd_usrp_sink_0.set_center_freq(center_freq, 0) 491 | self.uhd_usrp_sink_0.set_gain(hw_gain, 0) 492 | self.uhd_usrp_sink_0.set_antenna('TX/RX', 0) 493 | self.connect( 494 | (self.blocks_multiply_tune, 0), 495 | (self.uhd_usrp_sink_0, 0)) 496 | 497 | # this file sink provides an IQ capture of the RF 498 | # being transmitted by this app; the resulting file 499 | # is used for debugging only and should be disabled 500 | # under normal use 501 | if iq_file_out: 502 | self.blocks_file_sink_iq = blocks.file_sink( 503 | gr.sizeof_gr_complex*1, 504 | "takeover_output_c435M_s8M.iq", 505 | False) 506 | self.blocks_file_sink_iq.set_unbuffered(False) 507 | 508 | self.connect( 509 | (self.blocks_multiply_tune, 0), 510 | (self.blocks_file_sink_iq, 0) 511 | ) 512 | 513 | # attempts to decrease latency 514 | #self.blocks_message_source_0.set_max_noutput_items(128) 515 | #self.blocks_multiply_tune.set_max_noutput_items(512) 516 | buffer_size = 1 517 | self.blocks_message_source_0.set_max_output_buffer(buffer_size) 518 | self.blocks_vector_to_streams.set_max_output_buffer(buffer_size) 519 | 520 | 521 | def retune(self, new_freq): 522 | self.frequency = new_freq 523 | self.analog_sig_source_x_0.set_frequency( 524 | self.frequency - self.center_freq) 525 | 526 | # this method transfers data from a python list to the 527 | # flowgraph's message queue for transmission 528 | def fill_queue(self, txDataList): 529 | message_str = "" 530 | for bit in txDataList: 531 | if bit == 1: 532 | message_str += b'\x01' 533 | elif bit == 0: 534 | message_str += b'\x00' 535 | else: 536 | print("Error passing data to flowgraph. Exiting...") 537 | exit(1) 538 | self.source_queue.handle(gr.message_from_string(message_str)) 539 | message_str = "" 540 | #self.source_queue.insert_tail(gr.message_from_string(message_str)) 541 | 542 | 543 | # this method transfers data from a python list to the 544 | # flowgraph's message queue for transmission 545 | def fill_queue_vector(self, bb_list, hop_select_list): 546 | 547 | # baseband and select values must be the same 548 | if len(bb_list) != len(hop_select_list): 549 | print("Fatal Error: hop select list must be same length as baseband list") 550 | exit(1) 551 | # create a vector entry of the bb bits and corresponding enables 552 | #vector_sample = numpy.array([b'\x00', 0, 0, 0]) 553 | vector_sample = [b'\x00', b'\x00', b'\x00', b'\x00'] 554 | # now we make a u8 vector out of this 555 | #vector_pmt = pmt.make_u8vector(4, b'\x00') 556 | 557 | for i in xrange(len(bb_list)): 558 | # assign first vector element to bb value 559 | vector_sample[0] = b'\x01' if bb_list[i] == 1 else b'\x00' 560 | if hop_select_list[i] == 0: 561 | vector_sample[1] = b'\x01' 562 | vector_sample[2] = b'\x00' 563 | vector_sample[3] = b'\x00' 564 | elif hop_select_list[i] == 1: 565 | vector_sample[1] = b'\x00' 566 | vector_sample[2] = b'\x01' 567 | vector_sample[3] = b'\x00' 568 | elif hop_select_list[i] == 2: 569 | vector_sample[1] = b'\x00' 570 | vector_sample[2] = b'\x00' 571 | vector_sample[3] = b'\x01' 572 | else: 573 | print("Fatal Error: hop select out of range; must be 0-2") 574 | exit(1) 575 | 576 | #vector_pmt = pmt.to_pmt(vector_sample) 577 | vector_str = "" 578 | for uchar in vector_sample: 579 | vector_str += uchar 580 | message = gr.message_from_string(vector_str) 581 | self.source_queue_v.insert_tail(message) 582 | 583 | # this flowgraph flushes the buffers of all blocks 584 | def flush_all(self): 585 | return 0 586 | 587 | 588 | ############################################################## 589 | class ook_tx_flowgraph(gr.top_block): 590 | def __init__(self, center_freq, freq, samp_rate, gain, 591 | basebandFileName, basebandSampleRate, repeat): 592 | gr.top_block.__init__(self) 593 | 594 | ################################################## 595 | # Variables 596 | ################################################## 597 | #self.cutoff_freq = channel_width/2 598 | 599 | ################################################## 600 | # Blocks 601 | ################################################## 602 | self.blocks_file_source_0 = blocks.file_source(gr.sizeof_char*1, basebandFileName, repeat) 603 | self.blocks_repeat_0 = blocks.repeat(gr.sizeof_char*1, int(samp_rate/basebandSampleRate)) 604 | 605 | # setup osmocom block for HackRF control 606 | self.osmosdr_sink_0 = osmosdr.sink( args="numchan=" + str(1) + " " + "" ) 607 | self.osmosdr_sink_0.set_sample_rate(samp_rate) 608 | self.osmosdr_sink_0.set_center_freq(center_freq, 0) 609 | self.osmosdr_sink_0.set_freq_corr(0, 0) 610 | #self.osmosdr_sink_0.set_gain(14, 0) 611 | #self.osmosdr_sink_0.set_if_gain(20, 0) 612 | #self.osmosdr_sink_0.set_bb_gain(20, 0) 613 | self.osmosdr_sink_0.set_gain(0, 0) 614 | self.osmosdr_sink_0.set_if_gain(0, 0) 615 | self.osmosdr_sink_0.set_bb_gain(0, 0) 616 | self.osmosdr_sink_0.set_antenna("", 0) 617 | self.osmosdr_sink_0.set_bandwidth(0, 0) 618 | 619 | self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) 620 | self.blocks_float_to_complex_0 = blocks.float_to_complex(1) 621 | self.blocks_char_to_float_0 = blocks.char_to_float(1, 1) 622 | self.analog_sig_source_x_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq-center_freq, gain, 0) 623 | self.analog_const_source_x_0 = analog.sig_source_f(0, analog.GR_CONST_WAVE, 0, 0, 0) 624 | # message sink is primary method of getting baseband data into waveconverter 625 | #self.sink_queue = gr.msg_queue() 626 | #self.blocks_message_sink_0 = blocks.message_sink(gr.sizeof_char*1, self.sink_queue, False) 627 | 628 | # if directed, we also dump the output IQ data into a file 629 | #if len(dig_out_filename) > 0: 630 | #print("Outputing IQ to waveform to " + dig_out_filename) 631 | #self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_char*1, dig_out_filename, False) 632 | #self.blocks_file_sink_0.set_unbuffered(False) 633 | 634 | ################################################## 635 | # Connections 636 | ################################################## 637 | self.connect((self.analog_const_source_x_0, 0), (self.blocks_float_to_complex_0, 1)) 638 | self.connect((self.analog_sig_source_x_0, 0), (self.blocks_multiply_xx_0, 1)) 639 | self.connect((self.blocks_char_to_float_0, 0), (self.blocks_float_to_complex_0, 0)) 640 | self.connect((self.blocks_file_source_0, 0), (self.blocks_repeat_0, 0)) 641 | self.connect((self.blocks_float_to_complex_0, 0), (self.blocks_multiply_xx_0, 0)) 642 | self.connect((self.blocks_multiply_xx_0, 0), (self.osmosdr_sink_0, 0)) 643 | self.connect((self.blocks_repeat_0, 0), (self.blocks_char_to_float_0, 0)) 644 | 645 | ############################################################## 646 | class fsk_tx_flowgraph(gr.top_block): 647 | def __init__(self, center_freq, freq, samp_rate, fsk_deviation_hz, 648 | basebandFileName, basebandSampleRate, repeat): 649 | gr.top_block.__init__(self) 650 | 651 | ################################################## 652 | # Variables 653 | ################################################## 654 | #self.cutoff_freq = channel_width/2 655 | 656 | ################################################## 657 | # Blocks 658 | ################################################## 659 | self.blocks_file_source_0 = blocks.file_source(gr.sizeof_char*1, basebandFileName, repeat) 660 | self.blocks_repeat_0 = blocks.repeat(gr.sizeof_char*1, int(samp_rate/basebandSampleRate)) 661 | 662 | # setup osmocom block for HackRF control 663 | self.osmosdr_sink_0 = osmosdr.sink( args="numchan=" + str(1) + " " + "" ) 664 | self.osmosdr_sink_0.set_sample_rate(samp_rate) 665 | self.osmosdr_sink_0.set_center_freq(center_freq, 0) 666 | self.osmosdr_sink_0.set_freq_corr(0, 0) 667 | #self.osmosdr_sink_0.set_gain(14, 0) 668 | #self.osmosdr_sink_0.set_if_gain(20, 0) 669 | #self.osmosdr_sink_0.set_bb_gain(20, 0) 670 | self.osmosdr_sink_0.set_gain(0, 0) 671 | self.osmosdr_sink_0.set_if_gain(0, 0) 672 | self.osmosdr_sink_0.set_bb_gain(0, 0) 673 | self.osmosdr_sink_0.set_antenna("", 0) 674 | self.osmosdr_sink_0.set_bandwidth(0, 0) 675 | 676 | self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) 677 | self.blocks_float_to_complex_0 = blocks.float_to_complex(1) 678 | self.blocks_char_to_float_0 = blocks.char_to_float(1, 1) 679 | self.analog_sig_source_x_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq-center_freq, gain, 0) 680 | self.analog_const_source_x_0 = analog.sig_source_f(0, analog.GR_CONST_WAVE, 0, 0, 0) 681 | # message sink is primary method of getting baseband data into waveconverter 682 | #self.sink_queue = gr.msg_queue() 683 | #self.blocks_message_sink_0 = blocks.message_sink(gr.sizeof_char*1, self.sink_queue, False) 684 | 685 | # if directed, we also dump the output IQ data into a file 686 | #if len(dig_out_filename) > 0: 687 | #print("Outputing IQ to waveform to " + dig_out_filename) 688 | #self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_char*1, dig_out_filename, False) 689 | #self.blocks_file_sink_0.set_unbuffered(False) 690 | 691 | ################################################## 692 | # Connections 693 | ################################################## 694 | self.connect((self.analog_const_source_x_0, 0), (self.blocks_float_to_complex_0, 1)) 695 | self.connect((self.analog_sig_source_x_0, 0), (self.blocks_multiply_xx_0, 1)) 696 | self.connect((self.blocks_char_to_float_0, 0), (self.blocks_float_to_complex_0, 0)) 697 | self.connect((self.blocks_file_source_0, 0), (self.blocks_repeat_0, 0)) 698 | self.connect((self.blocks_float_to_complex_0, 0), (self.blocks_multiply_xx_0, 0)) 699 | self.connect((self.blocks_multiply_xx_0, 0), (self.osmosdr_sink_0, 0)) 700 | self.connect((self.blocks_repeat_0, 0), (self.blocks_char_to_float_0, 0)) 701 | 702 | 703 | 704 | 705 | ############################################################## 706 | # This flowgraph consists of the following blocks: 707 | # - a File Source that 708 | # - a Frequency Translating FIR filter that tunes to the target signal 709 | # - a quadrature demod block that demodules the FSK signal 710 | # - an Add Const block that shifts the demodulated signal downwards, centering 711 | # it around zero on the y-axis 712 | # - a Binary Slicer that converts centered signal from floating point to binary 713 | # - a File Sink that outputs 714 | 715 | class fsk_flowgraph(gr.top_block): 716 | def __init__(self, samp_rate_in, samp_rate_out, center_freq, 717 | tune_freq, channel_width, transition_width, threshold, fsk_deviation, fskSquelch, 718 | iq_filename, dig_out_filename): 719 | gr.top_block.__init__(self) 720 | 721 | ################################################## 722 | # Variables 723 | ################################################## 724 | self.cutoff_freq = channel_width/2 725 | self.firdes_taps = firdes.low_pass(1, samp_rate_in, 726 | self.cutoff_freq, 727 | transition_width) 728 | 729 | ################################################## 730 | # Blocks 731 | ################################################## 732 | self.blocks_file_source_0 = blocks.file_source(gr.sizeof_gr_complex*1, iq_filename, False) 733 | self.blocks_tuning_filter_0 = filter.freq_xlating_fir_filter_ccc(int(samp_rate_in/samp_rate_out), 734 | (self.firdes_taps), 735 | tune_freq-center_freq, 736 | samp_rate_in) 737 | self.analog_pwr_squelch_xx_0 = analog.pwr_squelch_cc(fskSquelch, 1, 1, False) 738 | self.blocks_quadrature_demod_0 = analog.quadrature_demod_cf(samp_rate_out/(2*pi*fsk_deviation/2)) 739 | self.blocks_add_const_vxx_0 = blocks.add_const_vff((-1*threshold, )) 740 | self.blocks_digital_binary_slicer_fb_0 = digital.binary_slicer_fb() 741 | 742 | # swapped message sink for file sink 743 | #self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_char*1, dig_out_filename, False) 744 | #self.blocks_file_sink_0.set_unbuffered(False) 745 | self.sink_queue = gr.msg_queue() 746 | self.blocks_message_sink_0 = blocks.message_sink(gr.sizeof_char*1, self.sink_queue, False) 747 | 748 | ################################################## 749 | # Connections 750 | ################################################## 751 | self.connect((self.blocks_file_source_0, 0), (self.blocks_tuning_filter_0, 0)) 752 | self.connect((self.blocks_tuning_filter_0, 0), (self.analog_pwr_squelch_xx_0, 0)) 753 | self.connect((self.analog_pwr_squelch_xx_0, 0), (self.blocks_quadrature_demod_0, 0)) 754 | self.connect((self.blocks_quadrature_demod_0, 0), (self.blocks_add_const_vxx_0, 0)) 755 | self.connect((self.blocks_add_const_vxx_0, 0), (self.blocks_digital_binary_slicer_fb_0, 0)) 756 | 757 | #self.connect((self.digital_binary_slicer_fb_0, 0), (self.blocks_file_sink_0, 0)) 758 | self.connect((self.blocks_digital_binary_slicer_fb_0, 0), (self.blocks_message_sink_0, 0)) 759 | 760 | -------------------------------------------------------------------------------- /rf_ntsc.py: -------------------------------------------------------------------------------- 1 | from gnuradio import analog 2 | from gnuradio import blocks 3 | from gnuradio import eng_notation 4 | from gnuradio import filter 5 | from gnuradio import gr 6 | from gnuradio.eng_option import eng_option 7 | from gnuradio.filter import firdes 8 | from gnuradio import zeromq 9 | from gnuradio import digital 10 | from gnuradio import uhd 11 | import pmt 12 | import math 13 | import osmosdr 14 | import numpy 15 | 16 | # global constants 17 | class ntsc_fm_demod_flowgraph(gr.top_block): 18 | def __init__(self, verbose, center_freq, freq, 19 | samp_rate, bb_samp_rate, 20 | fm_deviation, channel_width, transition_width, 21 | bb_lpf_cutoff, bb_lpf_transition, 22 | tcp_str, 23 | hw_sel, 24 | fifo_name = "", repeat=True, iq_file_name=""): 25 | 26 | gr.top_block.__init__(self) 27 | 28 | if verbose > 0: 29 | print("\nFlowgraph Properties:") 30 | print(" Center Frequency: {} MHz".format(center_freq/1000000.0)) 31 | print(" Tune Frequency: {} MHz".format(freq/1000000.0)) 32 | print(" IQ Sample Rate (in): {} MHz".format(samp_rate/1000000.0)) 33 | print(" BB Sample Rate (out): {} MHz".format(bb_samp_rate/1000000.0)) 34 | print(" FM Deviation: {} MHz".format(fm_deviation/1000000.0)) 35 | print(" Channel Width: {} MHz".format(channel_width/1000000.0)) 36 | print(" Transition Width: {} MHz".format(transition_width/1000000.0)) 37 | print(" BB LPF cutoff: {} MHz".format(bb_lpf_cutoff/1000000.0)) 38 | print(" BB LPF transition: {} MHz".format(bb_lpf_transition/1000000.0)) 39 | if hw_sel == 0: 40 | print(" SDR: HackRF") 41 | elif hw_sel == 1: 42 | print(" SDR: USRP") 43 | print(" FIFO Name: {}".format(fifo_name)) 44 | print(" Repeat: {}".format(repeat)) 45 | print(" IQ File Name: {}".format(iq_file_name)) 46 | 47 | # start by dumping baseband into a file and viewing in grc 48 | 49 | if verbose > 0: 50 | print("Entering NTSC Demodulator...") 51 | 52 | # variables 53 | self.center_freq = center_freq 54 | self.freq = freq 55 | self.samp_rate = samp_rate 56 | self.bb_samp_rate = bb_samp_rate 57 | self.fm_deviation = fm_deviation 58 | self.channel_width = channel_width 59 | self.transition_width = transition_width 60 | self.bb_lpf_cutoff = bb_lpf_cutoff 61 | self.bb_lpf_transition = bb_lpf_transition 62 | self.repeat = repeat 63 | self.iq_file_name = iq_file_name 64 | self.fifo_name = fifo_name 65 | self.hw_sel = hw_sel 66 | 67 | self.tuning_taps = firdes.low_pass(1, 68 | self.samp_rate, 69 | self.channel_width/2, 70 | self.channel_width/16) 71 | self.lpf_taps = firdes.low_pass(1, 72 | samp_rate, 73 | self.bb_lpf_cutoff, 74 | self.bb_lpf_transition, 75 | firdes.WIN_HAMMING, 76 | 6.76) 77 | 78 | # blocks 79 | 80 | # if we were not passed a file name, use the osmocom source 81 | if self.iq_file_name == "": 82 | if verbose > 0: 83 | print("Using SDR as input...") 84 | 85 | if self.hw_sel == 0: 86 | self.osmosdr_source_0 = osmosdr.source( 87 | args="numchan=" + str(1) + " " + '') 88 | self.osmosdr_source_0.set_sample_rate(samp_rate) 89 | self.osmosdr_source_0.set_center_freq(center_freq, 0) 90 | self.osmosdr_source_0.set_freq_corr(0, 0) 91 | self.osmosdr_source_0.set_dc_offset_mode(0, 0) 92 | self.osmosdr_source_0.set_iq_balance_mode(0, 0) 93 | self.osmosdr_source_0.set_gain_mode(False, 0) 94 | self.osmosdr_source_0.set_gain(10, 0) 95 | self.osmosdr_source_0.set_if_gain(20, 0) 96 | self.osmosdr_source_0.set_bb_gain(20, 0) 97 | self.osmosdr_source_0.set_antenna('', 0) 98 | self.osmosdr_source_0.set_bandwidth(0, 0) 99 | elif self.hw_sel == 1: 100 | self.uhd_usrp_source_0 = uhd.usrp_source( 101 | ",".join(("", "")), 102 | uhd.stream_args( 103 | cpu_format="fc32", 104 | channels=range(1), 105 | ), 106 | ) 107 | self.uhd_usrp_source_0.set_samp_rate(samp_rate) 108 | self.uhd_usrp_source_0.set_center_freq(center_freq, 0) 109 | self.uhd_usrp_source_0.set_gain(20, 0) 110 | self.uhd_usrp_source_0.set_antenna('RX2', 0) 111 | 112 | # otherwise, use a file source with throttle 113 | else: 114 | if verbose > 0: 115 | print("Using {} as input...".format(iq_file_name)) 116 | self.blocks_file_source_0 = blocks.file_source( 117 | gr.sizeof_gr_complex * 1, 118 | iq_file_name, 119 | repeat=repeat) 120 | self.blocks_throttle_0 = blocks.throttle( 121 | gr.sizeof_gr_complex * 1, samp_rate, True) 122 | self.connect( 123 | (self.blocks_file_source_0, 0), 124 | (self.blocks_throttle_0, 0) 125 | ) 126 | 127 | # simple tuner 128 | self.analog_sig_source_x_0 = analog.sig_source_c( 129 | samp_rate, 130 | analog.GR_COS_WAVE, 131 | center_freq - freq, 132 | 1, 133 | 0) 134 | self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) 135 | if iq_file_name == "": 136 | if self.hw_sel == 0: 137 | self.connect( 138 | (self.osmosdr_source_0, 0), 139 | (self.blocks_multiply_xx_0, 0) 140 | ) 141 | elif self.hw_sel == 1: 142 | self.connect( 143 | (self.uhd_usrp_source_0, 0), 144 | (self.blocks_multiply_xx_0, 0) 145 | ) 146 | else: 147 | self.connect( 148 | (self.blocks_throttle_0, 0), 149 | (self.blocks_multiply_xx_0, 0) 150 | ) 151 | self.connect( 152 | (self.analog_sig_source_x_0, 0), 153 | (self.blocks_multiply_xx_0, 1) 154 | ) 155 | 156 | """ 157 | # simple decimator 158 | self.keep = blocks.keep_one_in_n(gr.sizeof_gr_complex*1, 4) 159 | self.connect( 160 | (self.blocks_multiply_xx_0, 0), 161 | (self.keep, 0) 162 | ) 163 | """ 164 | 165 | # demod block 166 | self.analog_quadrature_demod_cf_0_0 = analog.quadrature_demod_cf( 167 | (samp_rate) / (2 * math.pi * self.fm_deviation / 8.0)) 168 | self.connect( 169 | (self.blocks_multiply_xx_0, 0), 170 | #(self.keep, 0), 171 | (self.analog_quadrature_demod_cf_0_0, 0) 172 | ) 173 | 174 | # low pass filter the demod output 175 | self.low_pass_filter_1 = filter.filter.fir_filter_fff( 176 | int(samp_rate/bb_samp_rate), 177 | self.lpf_taps 178 | ) 179 | self.connect( 180 | (self.analog_quadrature_demod_cf_0_0, 0), 181 | (self.low_pass_filter_1, 0) 182 | ) 183 | 184 | # output the baseband to a fifo 185 | if False: 186 | self.blocks_file_sink_0 = blocks.file_sink( 187 | gr.sizeof_float * 1, 188 | fifo_name, 189 | False) 190 | self.blocks_file_sink_0.set_unbuffered(False) 191 | self.connect( 192 | (self.low_pass_filter_1, 0), 193 | (self.blocks_file_sink_0, 0) 194 | ) 195 | 196 | """ 197 | # multiply by a constant and then convert to a short to reduce data 198 | self.multiply_const = blocks.multiply_const_vff((10000, )) # how to scale? 199 | self.float_to_short = blocks.float_to_short(1, 1) 200 | self.connect( 201 | (self.low_pass_filter_1, 0), 202 | (self.multiply_const, 0) 203 | ) 204 | self.connect( 205 | (self.multiply_const, 0), 206 | (self.float_to_short, 0) 207 | ) 208 | self.connect( 209 | (self.float_to_short, 0), 210 | (self.zeromq_push_sink_0, 0) 211 | ) 212 | """ 213 | 214 | # now add the ZMQ block for output to the main program 215 | self.zeromq_push_sink_0 = zeromq.push_sink(gr.sizeof_float, # gr.sizeof_short, 216 | 1, 217 | tcp_str, 218 | 100, 219 | False, 220 | 32768*8)#-1) 221 | 222 | self.connect( 223 | (self.low_pass_filter_1, 0), 224 | (self.zeromq_push_sink_0, 0) 225 | ) 226 | 227 | def update_freq(self, freq): 228 | self.freq = freq 229 | self.analog_sig_source_x_0.set_frequency( 230 | self.center_freq - self.freq) 231 | 232 | def update_center_freq(self, center_freq): 233 | self.center_freq = center_freq 234 | self.analog_sig_source_x_0.set_frequency( 235 | self.center_freq - self.freq) 236 | # can't adjust center frequency of hardware if we're running 237 | # with an IQ file 238 | if self.iq_file_name == "": 239 | if self.hw_sel == 0: 240 | self.osmosdr_source_0.set_center_freq(center_freq, 0) 241 | elif self.hw_sel == 1: 242 | self.uhd_usrp_source_0.set_center_freq(center_freq, 0) 243 | 244 | 245 | def update_lpf_cutoff(self, lpf_cutoff): 246 | self.bb_lpf_cutoff = lpf_cutoff 247 | self.lpf_taps = firdes.low_pass(1, 248 | samp_rate, 249 | self.bb_lpf_cutoff, 250 | self.bb_lpf_transition, 251 | firdes.WIN_HAMMING, 252 | 6.76) 253 | self.low_pass_filter_1.set_taps(self.lpf_taps) 254 | 255 | 256 | 257 | 258 | # this flowgraph uses a frequency Xlating FIR filter on the input and is 259 | # too computationally intensive; it has been replaced by a simpler multiply 260 | # tuner version 261 | class ntsc_fm_demod_flowgraph_fxfir(gr.top_block): 262 | def __init__(self, verbose, center_freq, freq, 263 | samp_rate, bb_samp_rate, 264 | fm_deviation, channel_width, transition_width, 265 | bb_lpf_cutoff, bb_lpf_transition, 266 | tcp_str, fifo_name = "", repeat=True, iq_file_name=""): 267 | 268 | gr.top_block.__init__(self) 269 | 270 | if verbose > 0: 271 | print("\nFlowgraph Properties:") 272 | print(" Center Frequency: {} MHz".format(center_freq/1000000.0)) 273 | print(" Tune Frequency: {} MHz".format(freq/1000000.0)) 274 | print(" IQ Sample Rate (in): {} MHz".format(samp_rate/1000000.0)) 275 | print(" BB Sample Rate (out): {} MHz".format(bb_samp_rate/1000000.0)) 276 | print(" FM Deviation: {} MHz".format(fm_deviation/1000000.0)) 277 | print(" Channel Width: {} MHz".format(channel_width/1000000.0)) 278 | print(" Transition Width: {} MHz".format(transition_width/1000000.0)) 279 | print(" BB LPF cutoff: {} MHz".format(bb_lpf_cutoff/1000000.0)) 280 | print(" BB LPF transition: {} MHz".format(bb_lpf_transition/1000000.0)) 281 | print(" FIFO Name: {}".format(fifo_name)) 282 | print(" Repeat: {}".format(repeat)) 283 | print(" IQ File Name: {}".format(iq_file_name)) 284 | 285 | # start by dumping baseband into a file and viewing in grc 286 | 287 | if verbose > 0: 288 | print("Entering NTSC Demodulator...") 289 | 290 | # variables 291 | self.center_freq = center_freq 292 | self.freq = freq 293 | self.samp_rate = samp_rate 294 | self.bb_samp_rate = bb_samp_rate 295 | self.fm_deviation = fm_deviation 296 | self.channel_width = channel_width 297 | self.transition_width = transition_width 298 | self.bb_lpf_cutoff = bb_lpf_cutoff 299 | self.bb_lpf_transition = bb_lpf_transition 300 | self.repeat = repeat 301 | self.iq_file_name = iq_file_name 302 | self.fifo_name = fifo_name 303 | 304 | self.tuning_taps = firdes.low_pass(1, 305 | self.samp_rate, 306 | self.channel_width/2, 307 | self.channel_width/16) 308 | self.lpf_taps = firdes.low_pass(1, 309 | samp_rate, 310 | self.bb_lpf_cutoff, 311 | self.bb_lpf_transition, 312 | firdes.WIN_HAMMING, 313 | 6.76) 314 | 315 | # blocks 316 | 317 | # if we were not passed a file name, use the osmocom source 318 | if self.iq_file_name == "": 319 | if verbose > 0: 320 | print("Using SDR as input...") 321 | self.osmosdr_source_0 = osmosdr.source( 322 | args="numchan=" + str(1) + " " + '') 323 | self.osmosdr_source_0.set_sample_rate(samp_rate) 324 | self.osmosdr_source_0.set_center_freq(center_freq, 0) 325 | self.osmosdr_source_0.set_freq_corr(0, 0) 326 | self.osmosdr_source_0.set_dc_offset_mode(0, 0) 327 | self.osmosdr_source_0.set_iq_balance_mode(0, 0) 328 | self.osmosdr_source_0.set_gain_mode(False, 0) 329 | self.osmosdr_source_0.set_gain(10, 0) 330 | self.osmosdr_source_0.set_if_gain(20, 0) 331 | self.osmosdr_source_0.set_bb_gain(20, 0) 332 | self.osmosdr_source_0.set_antenna('', 0) 333 | self.osmosdr_source_0.set_bandwidth(0, 0) 334 | # otherwise, use a file source with throttle 335 | else: 336 | if verbose > 0: 337 | print("Using {} as input...".format(iq_file_name)) 338 | self.blocks_file_source_0 = blocks.file_source( 339 | gr.sizeof_gr_complex * 1, 340 | iq_file_name, 341 | repeat=repeat) 342 | self.blocks_throttle_0 = blocks.throttle( 343 | gr.sizeof_gr_complex * 1, samp_rate, True) 344 | self.connect( 345 | (self.blocks_file_source_0, 0), 346 | (self.blocks_throttle_0, 0) 347 | ) 348 | 349 | # channel filter 350 | self.freq_xlating_fir_filter_ccc_0 = filter.freq_xlating_fir_filter_ccc( 351 | 1, 352 | self.tuning_taps, 353 | freq - center_freq, 354 | samp_rate) 355 | if iq_file_name == "": 356 | self.connect( 357 | (self.osmosdr_source_0, 0), 358 | (self.freq_xlating_fir_filter_ccc_0, 0) 359 | ) 360 | else: 361 | self.connect( 362 | (self.blocks_throttle_0, 0), 363 | (self.freq_xlating_fir_filter_ccc_0, 0) 364 | ) 365 | 366 | # demod block 367 | self.analog_quadrature_demod_cf_0_0 = analog.quadrature_demod_cf( 368 | (samp_rate) / (2 * math.pi * self.fm_deviation / 8.0)) 369 | self.connect( 370 | (self.freq_xlating_fir_filter_ccc_0, 0), 371 | (self.analog_quadrature_demod_cf_0_0, 0) 372 | ) 373 | 374 | # low pass filter the demod output 375 | self.low_pass_filter_1 = filter.filter.fir_filter_fff( 376 | int(samp_rate/bb_samp_rate), 377 | self.lpf_taps 378 | ) 379 | self.connect( 380 | (self.analog_quadrature_demod_cf_0_0, 0), 381 | (self.low_pass_filter_1, 0) 382 | ) 383 | 384 | # output the baseband to a fifo 385 | if False: 386 | self.blocks_file_sink_0 = blocks.file_sink( 387 | gr.sizeof_float * 1, 388 | fifo_name, 389 | False) 390 | self.blocks_file_sink_0.set_unbuffered(False) 391 | self.connect( 392 | (self.low_pass_filter_1, 0), 393 | (self.blocks_file_sink_0, 0) 394 | ) 395 | 396 | """ 397 | # multiply by a constant and then convert to a short to reduce data 398 | self.multiply_const = blocks.multiply_const_vff((10000, )) 399 | self.float_to_short = blocks.float_to_short(1, 1) 400 | self.connect( 401 | (self.low_pass_filter_1, 0), 402 | (self.multiply_const, 0) 403 | ) 404 | self.connect( 405 | (self.multiply_const, 0), 406 | (self.float_to_short, 0) 407 | ) 408 | self.connect( 409 | (self.float_to_short, 0), 410 | (self.zeromq_push_sink_0, 0) 411 | ) 412 | """ 413 | 414 | # now add the ZMQ block for output to the main program 415 | self.zeromq_push_sink_0 = zeromq.push_sink(gr.sizeof_float, # gr.sizeof_short, 416 | 1, 417 | tcp_str, 418 | 100, 419 | False, 420 | 32768*8)#-1) 421 | 422 | self.connect( 423 | (self.low_pass_filter_1, 0), 424 | (self.zeromq_push_sink_0, 0) 425 | ) 426 | 427 | def update_freq(self, freq): 428 | self.freq = freq 429 | self.freq_xlating_fir_filter_ccc_0.set_center_freq( 430 | self.freq - self.center_freq) 431 | 432 | def update_center_freq(self, center_freq): 433 | self.center_freq = center_freq 434 | self.osmosdr_source_0.set_center_freq(center_freq, 0) 435 | self.freq_xlating_fir_filter_ccc_0.set_center_freq( 436 | self.freq - self.center_freq) 437 | 438 | def update_lpf_cutoff(self, lpf_cutoff): 439 | self.bb_lpf_cutoff = lpf_cutoff 440 | self.lpf_taps = firdes.low_pass(1, 441 | samp_rate, 442 | self.bb_lpf_cutoff, 443 | self.bb_lpf_transition, 444 | firdes.WIN_HAMMING, 445 | 6.76) 446 | self.low_pass_filter_1.set_taps(self.lpf_taps) 447 | 448 | -------------------------------------------------------------------------------- /zmq_utils.py: -------------------------------------------------------------------------------- 1 | import zmq 2 | import array 3 | import time 4 | import struct 5 | import numpy as np 6 | import pmt 7 | import sys 8 | 9 | class zmq_pull_socket(): 10 | def __init__(self, tcp_str, verbose=0): 11 | self.context = zmq.Context() 12 | self.receiver = self.context.socket(zmq.PULL) 13 | self.receiver.connect(tcp_str) 14 | 15 | 16 | def poll(self, type_str='f', verbose=0): 17 | raw_data = self.receiver.recv() 18 | a = array.array(type_str, raw_data) 19 | return a 20 | 21 | def poll_message(self): 22 | msg = self.receiver.recv() 23 | # this is a binary string, convert it to a list of ints 24 | byte_list = [] 25 | for byte in msg: 26 | byte_list.append(ord(byte)) 27 | return byte_list 28 | 29 | # incomplete attempt to optimize data flow by 30 | # sending bytes instead of floats; flowgraph 31 | # changes needed to support this, as well 32 | # as all downstream code reworked to use 33 | # bytes 34 | def poll_short(self, type_str='h', verbose=0): 35 | raw_data = self.receiver.recv() 36 | a = array.array(type_str, raw_data) 37 | npa_s = np.asarray(a) 38 | npa_f = npa_s.astype(float) 39 | npa_f *= (1.0/10000.0) 40 | 41 | #fmt = "<%dI" % (len(raw_data) //4) 42 | #a = list(struct.unpack(fmt, raw_data)) 43 | return list(npa_f) 44 | --------------------------------------------------------------------------------