├── .gitignore ├── README.md ├── decoder ├── __init__.py ├── event_handler.py ├── rds_constants.py ├── rds_decoder.py └── tmc_events.py ├── fm.py ├── radio ├── __init__.py ├── diff_manchester.fsm ├── fm_audio_demod.py ├── fm_receiver.py ├── grc │ ├── .gitignore │ ├── diff_manchester.fsm │ ├── fm_audio_demod.grc │ ├── fm_receiver.grc │ ├── mpx_demod.grc │ ├── mpx_radio.grc │ ├── radio.grc │ └── rds_demod.grc ├── mpx_demod.py ├── rds_demod.py └── rds_synchronizer.py └── rds_handlers.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fm-rds 2 | A stereophonic FM broadcasting receiver and RDS (Radio Data System) decoder. 3 | 4 | Several python script for the radio demodulation and the RDS protocol decoding are provided. 5 | For studying purpose, we also provide GRC flowgraphs of the radio demodulator. 6 | 7 | ## Listening to a local station 8 | 9 | Download and extract the archive rds.tar.gz 10 | 11 | tar -xvzf rds.tar.gz 12 | 13 | Open the extracted folder and execute the program 14 | 15 | `cd rds` 16 | 17 | `./fm.py -c -s ` 18 | 19 | 20 | Where : 21 | 22 | * `card` is the kind of card you are usingn : ursp for NI/Ettus USRP, rtlsdr for dongles based on the RTL2832u ; 23 | * `sample_rate` is the sample rate at which you want your SDR receiver to operate. 24 | 25 | ## Opening the flowgraphs in gnuradio-companion 26 | The flowgraphs are located in the radio/grc folder. It contains four files : 27 | 28 | * `radio.grc`: the main flowgraph performing the audio and RDS demodulation (needs `fm_receiver.grc` to be built) ; 29 | * `fm_receiver.grc`: FM demodulate the incoming signal, yielding the multiplex containing audio channels L+R (Left plus Right), L-R and the RDS digital channel. 30 | * `mpx_demod.grc`: separates the L+R (Left plus Right), L-R and RDS channels of the MPX multiplex (needs `fm_audio_demod` and `rds_demod` to be built) ; 31 | * `fm_audio_demod.grc`: produces the stereo audio output ; 32 | * `rds_demod.grc`: produces the raw RDS bits output (in this file, you have to change the `prefix` variable to the complete path of `fm-rds/radio/grc`. 33 | -------------------------------------------------------------------------------- /decoder/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmrqt/fm-rds/9e53bff5b054c56ef45c9582f01b7bc85e26cb83/decoder/__init__.py -------------------------------------------------------------------------------- /decoder/event_handler.py: -------------------------------------------------------------------------------- 1 | class event(object): 2 | def __init__(self): 3 | self.handlers = set(); 4 | 5 | def add_handler(self, handler): 6 | self.handlers.add(handler); 7 | return self; 8 | 9 | def rm_handler(self, handler): 10 | try: 11 | self.handlers.remove(handler) 12 | except: 13 | raise ValueError("Handler is not handling this event, so cannot unhandle it.") 14 | return self; 15 | 16 | def fire(self, *args, **kargs): 17 | for handler in self.handlers: 18 | handler(*args, **kargs) 19 | -------------------------------------------------------------------------------- /decoder/rds_constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # page 77, Annex F in the standard 4 | pty_table=[ 5 | "None", 6 | "News", 7 | "Current Affairs", 8 | "Information", 9 | "Sport", 10 | "Education", 11 | "Drama", 12 | "Cultures", 13 | "Science", 14 | "Varied Speech", 15 | "Pop Music", 16 | "Rock Music", 17 | "Easy Listening", 18 | "Light Classics M", 19 | "Serious Classics", 20 | "Other Music", 21 | "Weather & Metr", 22 | "Finance", 23 | "Children's Progs", 24 | "Social Affairs", 25 | "Religion", 26 | "Phone In", 27 | "Travel & Touring", 28 | "Leisure & Hobby", 29 | "Jazz Music", 30 | "Country Music", 31 | "National Music", 32 | "Oldies Music", 33 | "Folk Music", 34 | "Documentary", 35 | "Alarm Test", 36 | "Alarm-Alarm!"]; 37 | 38 | # page 71, Annex D, table D.1 in the standard */ 39 | pi_country_codes=[ 40 | ["DE","GR","MA","__","MD"], 41 | ["DZ","CY","CZ","IE","EE"], 42 | ["AD","SM","PL","TR","__"], 43 | ["IL","CH","VA","MK","__"], 44 | ["IT","JO","SK","__","__"], 45 | ["BE","FI","SY","__","UA"], 46 | ["RU","LU","TN","__","__"], 47 | ["PS","BG","__","NL","PT"], 48 | ["AL","DK","LI","LV","SI"], 49 | ["AT","GI","IS","LB","__"], 50 | ["HU","IQ","MC","__","__"], 51 | ["MT","GB","LT","HR","__"], 52 | ["DE","LY","YU","__","__"], 53 | ["__","RO","ES","SE","__"], 54 | ["EG","FR","NO","BY","BA"]]; 55 | 56 | # page 72, Annex D, table D.2 in the standard */ 57 | coverage_area_codes=[ 58 | "Local", 59 | "International", 60 | "National", 61 | "Supra-regional", 62 | "Regional 1", 63 | "Regional 2", 64 | "Regional 3", 65 | "Regional 4", 66 | "Regional 5", 67 | "Regional 6", 68 | "Regional 7", 69 | "Regional 8", 70 | "Regional 9", 71 | "Regional 10", 72 | "Regional 11", 73 | "Regional 12"]; 74 | 75 | rds_group_acronyms=[ 76 | "BASIC", 77 | "PIN/SL", 78 | "RT", 79 | "AID", 80 | "CT", 81 | "TDC", 82 | "IH", 83 | "RP", 84 | "TMC", 85 | "EWS", 86 | "___", 87 | "___", 88 | "___", 89 | "___", 90 | "EON", 91 | "___"]; 92 | 93 | # page 74, Annex E, table E.1 in the standard: that's the ASCII table!!! */ 94 | 95 | # see page 84, Annex J in the standard */ 96 | language_codes=[ 97 | "Unkown/not applicable", 98 | "Albanian", 99 | "Breton", 100 | "Catalan", 101 | "Croatian", 102 | "Welsh", 103 | "Czech", 104 | "Danish", 105 | "German", 106 | "English", 107 | "Spanish", 108 | "Esperanto", 109 | "Estonian", 110 | "Basque", 111 | "Faroese", 112 | "French", 113 | "Frisian", 114 | "Irish", 115 | "Gaelic", 116 | "Galician", 117 | "Icelandic", 118 | "Italian", 119 | "Lappish", 120 | "Latin", 121 | "Latvian", 122 | "Luxembourgian", 123 | "Lithuanian", 124 | "Hungarian", 125 | "Maltese", 126 | "Dutch", 127 | "Norwegian", 128 | "Occitan", 129 | "Polish", 130 | "Portuguese", 131 | "Romanian", 132 | "Romansh", 133 | "Serbian", 134 | "Slovak", 135 | "Slovene", 136 | "Finnish", 137 | "Swedish", 138 | "Turkish", 139 | "Flemish", 140 | "Walloon"]; 141 | 142 | # see page 12 in ISO 14819-1 */ 143 | tmc_duration=[ 144 | ["no duration given", "no duration given"], 145 | ["15 minutes", "next few hours"], 146 | ["30 minutes", "rest of the day"], 147 | ["1 hour", "until tomorrow evening"], 148 | ["2 hours", "rest of the week"], 149 | ["3 hours", "end of next week"], 150 | ["4 hours", "end of the month"], 151 | ["rest of the day", "long period"]]; 152 | 153 | # optional message content, data field lengths and labels 154 | # see page 15 in ISO 14819-1 */ 155 | optional_content_lengths=[3,3,5,5,5,8,8,8,8,11,16,16,16,16,0,0]; 156 | 157 | label_descriptions=[ 158 | "Duration", 159 | "Control code", 160 | "Length of route affected", 161 | "Speed limit advice", 162 | "Quantifier", 163 | "Quantifier", 164 | "Supplementary information code", 165 | "Explicit start time", 166 | "Explicit stop time", 167 | "Additional event", 168 | "Detailed diversion instructions", 169 | "Destination", 170 | "RFU (Reserved for future use)", 171 | "Cross linkage to source of problem, or another route", 172 | "Separator", 173 | "RFU (Reserved for future use)"]; 174 | -------------------------------------------------------------------------------- /decoder/rds_decoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pmt 4 | import rds_constants 5 | import tmc_events 6 | from event_handler import event 7 | from gnuradio import gr 8 | import datetime 9 | 10 | class rds_decoder(gr.sync_block): 11 | def __init__(self): 12 | #Parent constructor 13 | gr.sync_block.__init__(self, name="RDS decoder", in_sig=None, out_sig=None); 14 | 15 | #Register message ports 16 | self.message_port_register_in(pmt.intern('Group')); 17 | self.set_msg_handler(pmt.intern('Group'), self.decode_group); 18 | 19 | #Attributes and events definitions 20 | self.reset_attr(); 21 | 22 | #Event definition 23 | self.af_event = event(); 24 | self.af_nb_af_to_follow_event = event(); 25 | self.pty_event = event(); 26 | self.pi_event = event(); 27 | self.rt_event = event(); 28 | self.ps_name_event = event(); 29 | self.di_event = event(); 30 | self.paging_opc_event = event(); 31 | self.paging_id_event = event(); 32 | self.paging_code_event = event(); 33 | self.broadcasters_codes_event = event(); 34 | self.ews_id_event = event(); 35 | self.ews_id_event = event(); 36 | self.ews_id_event = event(); 37 | self.na1_event = event(); 38 | self.na2_event = event(); 39 | self.language_code_event = event(); 40 | self.tmc_id_event = event(); 41 | self.ecc_event = event(); 42 | self.pin_event = event(); 43 | self.paging_opc_event = event(); 44 | self.paging_id_event = event(); 45 | self.paging_code_event = event(); 46 | self.oda_application_group_type_code_event = event(); 47 | self.oda_message_event = event(); 48 | self.oda_aid_event = event(); 49 | self.date_event = event(); 50 | self.eon_new_event = event(); #Event fired with PI(ON) 51 | self.eon_change_event = event(); #Event fired with PI(ON) 52 | self.tp_event = event(); 53 | self.ta_event = event(); 54 | self.ms_event = event(); 55 | 56 | def reset_attr(self): 57 | #Attributes definitions 58 | #Alternative Frequencies (AF) 59 | self.af_list = []; 60 | self.af_lfmf = False; 61 | self.nb_af_to_follow = 0; 62 | 63 | #Program Type (PTY) 64 | self.pty = 0 65 | self.static_pty_flag = False; 66 | 67 | #Program Information (PI) 68 | self.pi = 0; 69 | self.pi_country_identification = 0; 70 | self.pi_area_coverage = 0; 71 | self.pi_program_reference_number = 0; 72 | 73 | #RadioText (RT) 74 | self.rt = [' '] * 64; 75 | self.rt_ab_flag = False; 76 | 77 | #Program Service name (PS) 78 | self.ps_name = [' '] * 8; 79 | 80 | #Decoder Identification (DI) 81 | self.di = 0; 82 | self.mono_stereo_flag = False; 83 | self.artificial_head_flag = False; 84 | self.compressed_flag = False; 85 | 86 | #Paging 87 | self.paging_codes = 0; 88 | self.paging_opc = 0; 89 | self.paging_id = 0; 90 | 91 | #Program Item Number (PIN) 92 | self.pin_day = 0; 93 | self.pin_hour = 0; 94 | self.pin_minute = 0; 95 | 96 | ##Slow labelling codes 97 | #Extended Country Code (ECC) 98 | self.ecc = 0; 99 | #Traffic Message Channel (TMC) 100 | self.tmc_id = 0; 101 | #Language code 102 | self.language_code = 0; 103 | #Not assignated 104 | self.na1 = 0; 105 | self.na2 = 0; 106 | #For use by broadcasters 107 | self.broadcasters_codes = 0; 108 | #Emergency Warning Systems (EWS) 109 | self.ews_id = 0; 110 | 111 | #Open Data Application 112 | self.oda_application_group_type_code = 0; 113 | self.oda_message = 0; 114 | self.oda_aid = 0; 115 | 116 | #ClockTime (CT) 117 | self.clocktime = None; 118 | 119 | #Enhanced Other Networks information (EON) 120 | self.on_dict = {}; #Key: PI(ON), Value: eon object 121 | 122 | #Traffic Program (TP) 123 | self.tp_flag = False; 124 | #Traffic Announcement (TA) 125 | self.ta_flag = False; 126 | #Music/Speech 127 | self.ms_flag = False; 128 | 129 | def decode_group(self, group_pmt): 130 | group=pmt.to_python(group_pmt); 131 | group_type = ((group[1] >> 12) & 0xf); 132 | ab = (group[1] >> 11 ) & 0x1; 133 | 134 | #PI 135 | pi = group[0]; 136 | if(pi != self.pi): 137 | self.pi = pi; 138 | self.pi_country_identification = (self.pi >> 12) & 0xf; 139 | self.pi_area_coverage = (self.pi >> 8) & 0xf; 140 | self.pi_program_reference_number = self.pi & 0xff; 141 | #Fire event 142 | self.pi_event.fire(self); 143 | 144 | #PTY 145 | pty = (group[1] >> 5) & 0x1f; 146 | if(pty != self.pty): 147 | self.pty = pty; 148 | #Fire event 149 | self.pty_event.fire(self); 150 | 151 | #TP 152 | tp_flag = (group[1] >> 10) & 0x01; 153 | if(tp_flag != self.tp_flag): 154 | self.tp_flag = tp_flag; 155 | #Fire event 156 | self.tp_event.fire(self); 157 | 158 | #Specific decoding depending on group type 159 | if(group_type == 0): 160 | self.decode_type0(group, ab); 161 | elif(group_type == 1): 162 | self.decode_type1(group, ab); 163 | elif(group_type == 2): 164 | self.decode_type2(group, ab); 165 | elif(group_type == 3): 166 | self.decode_type3(group, ab); 167 | elif(group_type == 4): 168 | self.decode_type4(group, ab); 169 | elif(group_type == 5): 170 | self.decode_type5(group, ab); 171 | elif(group_type == 6): 172 | self.decode_type6(group, ab); 173 | elif(group_type == 7): 174 | self.decode_type7(group, ab); 175 | elif(group_type == 8): 176 | self.decode_type8(group, ab); 177 | elif(group_type == 9): 178 | self.decode_type9(group, ab); 179 | elif(group_type == 10): 180 | self.decode_type10(group, ab); 181 | elif(group_type == 11): 182 | self.decode_type11(group, ab); 183 | elif(group_type == 12): 184 | self.decode_type12(group, ab); 185 | elif(group_type == 13): 186 | self.decode_type13(group, ab); 187 | elif(group_type == 14): 188 | self.decode_type14(group, ab); 189 | elif(group_type == 15): 190 | self.decode_type15(group, ab); 191 | 192 | def decode_type0(self, group, B): 193 | af_code_1 = 0; 194 | af_code_2 = 0; 195 | no_af = 0; 196 | af_1 = 0; 197 | af_2 = 0; 198 | ps_name = list(self.ps_name); 199 | 200 | #TA 201 | ta_flag = (group[1] >> 4) & 0x01; 202 | if (ta_flag != self.ta_flag): 203 | self.ta_flag = ta_flag; 204 | #Fire event 205 | self.ta_event.fire(self); 206 | 207 | #MS 208 | ms_flag = (group[1] >> 3) & 0x01; 209 | if (ms_flag != self.ms_flag): 210 | self.ms_flag = ms_flag; 211 | #Fire event 212 | self.ms_event.fire(self); 213 | 214 | #DI 215 | di = (group[1] >> 2) & 0x01; 216 | segment_address = group[1] & 0x03; #DI segment 217 | if(di != self.di): 218 | # see page 41, table 9 of the standard 219 | if(segment_address==0): 220 | self.mono_stereo_flag=di; 221 | elif(segment_address==1): 222 | self.artificial_head_flag=di; 223 | elif(segment_address==2): 224 | self.compressed_flag=di; 225 | elif(segment_address==3): 226 | self.static_pty_flag=di; 227 | #Fire event 228 | self.di_event.fire(self); 229 | 230 | ps_name[segment_address * 2] = chr((group[3] >> 8) & 0xff); 231 | ps_name[segment_address * 2 + 1] = chr(group[3] & 0xff); 232 | if(ps_name != self.ps_name): 233 | self.ps_name = ps_name; 234 | self.ps_name_event.fire(self); 235 | 236 | 237 | if(not B): # type 0A 238 | #Decode AFs 239 | af_1 = self.decode_af((group[2] >> 8) & 0xff); 240 | af_2 = self.decode_af((group[2]) & 0xff); 241 | af = False; 242 | 243 | #If new AFs found, add to AF list 244 | if((af_1 != 0) and (af_1 not in self.af_list)): 245 | self.af_list.append(af_1); 246 | af = True; 247 | 248 | if((af_2 != 0) and (af_2 not in self.af_list)): 249 | self.af_list.append(af_2); 250 | af = True; 251 | 252 | #If new AFs found, fire event 253 | if(af): 254 | self.af_event.fire(self); 255 | 256 | def decode_af(self, af_code): 257 | alt_frequency = 0; # in kHz 258 | 259 | if((af_code == 0) or # not to be used 260 | (af_code == 205) or # filler code 261 | (af_code in range(206, 224)) or # not assigned 262 | (af_code == 224) or # No AF exists 263 | (af_code in range(251, 256))): # not assigned 264 | return 0; 265 | 266 | if(af_code in range(225, 250)): # VHF frequencies follow 267 | self.af_lfmf = False; 268 | self.nb_af_to_follow = af_code - 224; 269 | self.af_nb_af_to_follow_event.fire(); 270 | return 0; 271 | 272 | if(af_code == 250): # One LF/MF frequency follows 273 | self.af_lfmf = True; 274 | self.nb_af_to_follow = 1; 275 | self.af_nb_af_to_follow_event.fire(); 276 | return 0; 277 | 278 | if(self.af_lfmf): 279 | if(af_code in range(1, 16)): # LF (153-279kHz) 280 | alt_frequency = 153 + (af_code - 1) * 9; 281 | elif(af_code in range(16, 136)): # MF (531-1602kHz) 282 | alt_frequency = 531 + (af_code - 16) * 9; 283 | else: 284 | return 0; 285 | else: # VHF (87.6-107.9MHz) 286 | if(af_code in range(1, 206)): 287 | alt_frequency = 1000 * (af_code*0.1 + 87.5); 288 | else: 289 | return 0; 290 | 291 | return alt_frequency; 292 | 293 | def decode_type1(self, group, B): 294 | ecc = 0; 295 | paging = 0; 296 | country_code = (group[0] >> 12) & 0x0f; 297 | 298 | self.paging_codes = group[1] & 0x1f; 299 | #Fire event 300 | if (self.paging_codes): 301 | self.paging_code_event.fire(self); 302 | 303 | variant_code = (group[2] >> 12) & 0x7; 304 | slow_labelling = group[2] & 0xfff; 305 | 306 | day = (group[3] >> 11) & 0x1f; 307 | hour = (group[3] >> 6) & 0x1f; 308 | minute = group[3] & 0x3f; 309 | 310 | if(day or hour or minute): 311 | self.pin_day = day; 312 | self.pin_hour = hour; 313 | self.pin_minute = minute; 314 | #Fire event 315 | self.pin_event.fire(self); 316 | 317 | if(variant_code==0): # paging + ecc 318 | paging_opc = (slow_labelling >> 8) & 0x0f; 319 | ecc = slow_labelling & 0xff; 320 | 321 | if(self.paging_opc != paging_opc): 322 | self.paging_opc = paging_opc; 323 | #Fire event 324 | self.paging_opc_event.fire(self); 325 | if(self.ecc != ecc): 326 | self.ecc= ecc; 327 | #Fire event 328 | self.ecc_event.fire(self); 329 | 330 | elif(variant_code==1): #TMC identification 331 | if(self.tmc_id != slow_labelling): 332 | self.tmc = slow_labelling; 333 | #Fire event 334 | self.tmc_id_event.fire(self); 335 | 336 | elif(variant_code==2): #Paging identification 337 | if(self.paging_id != slow_labelling): 338 | self.paging_id = slow_labelling; 339 | #Fire event 340 | self.paging_id_event.fire(self); 341 | 342 | elif(variant_code==3): #Language codes 343 | if(self.language_code != slow_labelling): 344 | self.language_code = slow_labelling; 345 | #Fire event 346 | self.language_code_event.fire(self); 347 | 348 | elif(variant_code==4): #Not assignated (1) 349 | if(self.na1 != slow_labelling): 350 | self.na1 = slow_labelling; 351 | #Fire event 352 | self.na1_event.fire(self); 353 | 354 | elif(variant_code==5): #Not assignated (2) 355 | if(self.na2 != slow_labelling): 356 | self.na2 = slow_labelling; 357 | #Fire event 358 | self.na2_event.fire(self); 359 | 360 | elif(variant_code==6): #For use by broadcasters 361 | if(self.broadcasters_codes != slow_labelling): 362 | self.broadcasters_codes= slow_labelling; 363 | #Fire event 364 | self.broadcasters_codes_event.fire(self); 365 | 366 | elif(variant_code==7): #EWS 367 | if(self.ews_id != slow_labelling): 368 | self.ews_id= slow_labelling; 369 | #Fire event 370 | self.ews_id_event.fire(self); 371 | 372 | def decode_type2(self, group, B): #RadioText 373 | text_segment_address_code = group[1] & 0x0f; 374 | rt = list(self.rt); 375 | 376 | # when the A/B flag is toggled, flush your current rt 377 | if(self.rt_ab_flag != ((group[1] >> 4) & 0x01)): 378 | for i in range(0, 64): 379 | self.rt[i] = ' '; 380 | 381 | self.rt_ab_flag = (group[1] >> 4) & 0x01; 382 | 383 | if(not B): 384 | rt[text_segment_address_code *4 ] = chr((group[2] >> 8) & 0xff); 385 | rt[text_segment_address_code * 4 + 1] = chr(group[2] & 0xff); 386 | rt[text_segment_address_code * 4 + 2] = chr((group[3] >> 8) & 0xff); 387 | rt[text_segment_address_code * 4 + 3] = chr(group[3] & 0xff); 388 | else: 389 | rt[text_segment_address_code * 2 ] = chr((group[3] >> 8) & 0xff); 390 | rt[text_segment_address_code * 2 + 1] = chr(group[3] & 0xff); 391 | 392 | if(rt != self.rt): 393 | self.rt=rt; 394 | self.rt_event.fire(self); 395 | 396 | def decode_type3(self, group, B): 397 | if(B): #ODA 398 | return; 399 | 400 | if(self.oda_application_group_type_code != group[1]): 401 | self.oda_application_group_type_code = group[1]; 402 | #Fire event 403 | self.oda_application_group_type_code_event.fire(self); 404 | 405 | if(self.oda_message != group[2]): 406 | self.oda_message = group[2]; 407 | #Fire event 408 | self.oda_message_event.fire(self); 409 | 410 | if(self.oda_aid != group[3]): 411 | self.oda_aid = group[3]; 412 | #Fire event 413 | self.oda_aid_event.fire(self); 414 | 415 | def decode_type4(self, group, B): #ClockTime 416 | if(B): #ODA 417 | return; 418 | 419 | hour = ((group[2] & 0x1) << 4) | ((group[3] >> 12) & 0x0f); 420 | minute = (group[3] >> 6) & 0x3f; 421 | 422 | modified_julian_date = ((group[1] & 0x03) << 15) | ((group[2] >> 1) & 0x7fff); 423 | year = int((modified_julian_date - 15078.2) / 365.25); 424 | month = int((modified_julian_date - 14956.1 - (year * 365.25)) / 30.6001); 425 | day = modified_julian_date - 14956 - int(year * 365.25) - int(month * 30.6001); 426 | 427 | month -= 1; 428 | year += 1900; 429 | 430 | if ((month == 14) or (month == 15)): 431 | year += 1; 432 | month -= 12; 433 | 434 | time_offset_sign=1 - ((group[3]>>5)&0x1) * 2; 435 | time_offset=(group[3]&0x1F); 436 | minute_offset=time_offset_sign * 30 * time_offset; 437 | 438 | #Do nothing if date values are corrupted 439 | if((year < 1900) or (month < 1) or (month > 12) or (day < 1) 440 | or (day > 31) or (hour > 23) or (minute > 59) or (time_offset > 24)): 441 | return; 442 | 443 | tzinfo=ct_tz_data(minute_offset); 444 | self.clocktime=datetime.datetime(year, month, day, hour, minute, tzinfo=tzinfo); 445 | 446 | #Fire event 447 | self.date_event.fire(self); 448 | 449 | def decode_type5(self, group, B): #ODA 450 | return; 451 | 452 | def decode_type6(self, group, B): #ODA 453 | return; 454 | 455 | def decode_type7(self, group, B): #ODA 456 | return; 457 | 458 | def decode_type8(self, group, B): #ODA/TMC 459 | return; 460 | 461 | def decode_type9(self, group, B): #ODA/EWS 462 | return; 463 | 464 | def decode_type10(self, group, B): #ODA/PTYN 465 | return; 466 | 467 | def decode_type11(self, group, B): #ODA 468 | return; 469 | 470 | def decode_type12(self, group, B): #ODA 471 | return; 472 | 473 | def decode_type13(self, group, B): #ODA/Enhanced paging 474 | return; 475 | 476 | def decode_type14(self, group, B): #EON 477 | new_on_flag = False; 478 | variant_code= group[1] & 0x0f; 479 | information = group[2]; 480 | pi_on = group[3]; 481 | 482 | #Tuned network infos 483 | pty_tn = (group[1] >> 5) & 0x1F; 484 | if (pty_tn != self.pty): 485 | self.pty = pty_tn; 486 | #Fire event 487 | self.pty_event.fire(self); 488 | 489 | tp_flag_tn = (group[1] >> 10) & 0x01; 490 | if (tp_flag_tn != self.tp_flag): 491 | self.tp_flag = tp_flag_tn; 492 | #Fire event 493 | self.tp_event.fire(self); 494 | 495 | #Retrieve or create eon obect 496 | if (pi_on in self.on_dict): 497 | on = self.on_dict[pi_on]; 498 | else: 499 | on = eon(); 500 | on.pi = pi_on; 501 | self.on_dict[pi_on] = on; 502 | new_on_flag = True; 503 | 504 | #ON TP flag 505 | on.tp_flag = (group[1] >> 4) & 0x01; 506 | 507 | if (not B): 508 | if (variant_code >= 0 and variant_code <= 3): # PS(ON) 509 | on.ps_name[variant_code * 2 ] = chr((information >> 8) & 0xff); 510 | on.ps_name[variant_code * 2 + 1] = chr(information & 0xff); 511 | 512 | elif (variant_code == 4): # AF 513 | af_1 = 100.0 * (((information >> 8) & 0xff) + 875); 514 | af_2 = 100.0 * ((information & 0xff) + 875); 515 | if(af_1 not in self.af_list): 516 | on.af_list.append(af_1); 517 | if(af_2 not in self.af_list): 518 | on.af_list.append(af_2); 519 | 520 | elif (variant_code >= 5 and variant_code <= 8): # mapped frequencies 521 | af_1 = 100.0 * (((information >> 8) & 0xff) + 875); 522 | af_2 = 100.0 * ((information & 0xff) + 875); 523 | if(af_1 not in self.af_list): 524 | on.af_list.append(af_1); 525 | if(af_2 not in self.af_list): 526 | on.af_list.append(af_2); 527 | 528 | elif (variant_code == 9): # mapped frequencies (AM) 529 | af_1 = 100.0 * (((information >> 8) & 0xff) + 875); 530 | af_2 = (9.0 * ((information & 0xff) - 16) + 531)/1000; 531 | if(af_1 not in self.af_list): 532 | on.af_list.append(af_1); 533 | if(af_2 not in self.af_list): 534 | on.af_list.append(af_2); 535 | 536 | elif (variant_code == 10): # unallocated 537 | on.variant10 = information; 538 | 539 | elif (variant_code == 11): # unallocated 540 | on.variant11 = information; 541 | 542 | elif (variant_code == 12): # linkage information 543 | on.linkage_info = information; 544 | 545 | elif (variant_code == 13): # PTY(ON), TA(ON) 546 | on.ta_flag = information & 0x01; 547 | on.pty = (information >> 11) & 0x1f; 548 | 549 | elif (variant_code == 14): # PIN(ON) 550 | on.pin_day = (information >> 11) & 0x1f; 551 | on.pin_hour = (information >> 6) & 0x1f; 552 | on.pin_minute = information & 0x3f; 553 | else: #type B 554 | on.ta_flag = (group[1] >> 3) & 0x01; 555 | 556 | def decode_type15(self, group, B): 557 | return; 558 | 559 | #EON Class 560 | class eon(object): 561 | def __init__(self): 562 | #PI 563 | self.pi = 0; 564 | 565 | #Program Service name (PS) 566 | self.ps_name = []; 567 | for i in range(0, 8): 568 | self.ps_name.append(' '); 569 | 570 | #Alternative Frequencies (AF) 571 | self.af_list = []; 572 | 573 | #Variant 10 (unallocated) 574 | self.variant10 = 0; 575 | 576 | #Variant 11 (unallocated) 577 | self.variant11 = 0; 578 | 579 | #Linkage information 580 | self.linkage_info = 0; 581 | 582 | #PTY 583 | self.pty = 0; 584 | 585 | #TA 586 | self.ta_flag = False; 587 | 588 | #TP 589 | self.tp_flag = False; 590 | 591 | #PIN 592 | self.pin_day = 0; 593 | self.pin_hour = 0; 594 | self.pin_minute = 0; 595 | 596 | #Variant 15 (reserved for broadcasters use) 597 | self.variant15 = 0; 598 | 599 | #Timezone class, to be used with datetime class for CT 600 | class ct_tz_data(datetime.tzinfo): 601 | def __init__(self, minute_offset): 602 | self.minute_offset=minute_offset; 603 | 604 | def utcoffset(self, dt): 605 | return datetime.timedelta(minutes=self.minute_offset); 606 | 607 | def dst(self, dt): 608 | return datetime.timedelta(0); 609 | -------------------------------------------------------------------------------- /fm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import radio.fm_receiver as fmr 4 | import radio.rds_synchronizer as rds_sync 5 | import decoder.rds_decoder as rds_dec 6 | import rds_handlers as rds_handlers 7 | import threading 8 | import time 9 | import sys 10 | import getopt 11 | 12 | from PyQt4 import Qt 13 | from PyQt4.QtCore import QObject, pyqtSlot 14 | import PyQt4.Qwt5 as Qwt 15 | 16 | from gnuradio import audio 17 | from gnuradio import blocks 18 | from gnuradio import eng_notation 19 | from gnuradio import filter 20 | from gnuradio import gr 21 | from gnuradio import qtgui 22 | from gnuradio import uhd 23 | from gnuradio.filter import firdes 24 | import osmosdr 25 | import sip 26 | 27 | class radio(gr.top_block, Qt.QWidget): 28 | def __init__(self, tuner, gain, card, samp_rate): 29 | #Gnuradio top_block constructor 30 | gr.top_block.__init__(self, "Top Block") 31 | 32 | #QT widget constructor 33 | Qt.QWidget.__init__(self) 34 | self.setWindowTitle("FM Radio") 35 | try: 36 | self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc')) 37 | except: 38 | pass 39 | self.top_scroll_layout = Qt.QVBoxLayout() 40 | self.setLayout(self.top_scroll_layout) 41 | self.top_scroll = Qt.QScrollArea() 42 | self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame) 43 | self.top_scroll_layout.addWidget(self.top_scroll) 44 | self.top_scroll.setWidgetResizable(True) 45 | self.top_widget = Qt.QWidget() 46 | self.top_scroll.setWidget(self.top_widget) 47 | self.top_layout = Qt.QVBoxLayout(self.top_widget) 48 | self.top_grid_layout = Qt.QGridLayout() 49 | self.top_layout.addLayout(self.top_grid_layout) 50 | 51 | self.settings = Qt.QSettings("GNU Radio", "top_block") 52 | self.restoreGeometry(self.settings.value("geometry").toByteArray()) 53 | 54 | ################################################## 55 | # Attributes 56 | ################################################## 57 | self.tuner = tuner 58 | self.freq_offset = freq_offset = 0; 59 | self.gain = gain 60 | 61 | self.samp_rate = samp_rate; 62 | self.audio_rate = audio_rate = 48000; 63 | self.fm_sample_rate= fm_sample_rate = 512e3; 64 | 65 | ################################################## 66 | # Variables 67 | ################################################## 68 | fm_start = 87.5e6; 69 | fm_stop = 108e6; 70 | rtlsdr_gains = [0.0, 0.9, 1.4, 2.7, 3.7, 7.7, 8.7, 12.5, 14.4, 15.7, 71 | 16.6, 19.7, 20.7, 22.9, 25.4, 28.0, 29.7, 32.8, 33.8, 36.4, 72 | 37.2, 38.6, 40.2, 42.1, 43.4, 43.9, 44.5, 48.0, 49.6]; 73 | 74 | ################################################## 75 | # Blocks 76 | ################################################## 77 | if(card == "rtlsdr"): 78 | self.freq_offset=freq_offset=250e3; 79 | self.source = osmosdr.source( args="numchan=" + str(1) + " " + "" ) 80 | self.source.set_sample_rate(samp_rate) 81 | self.source.set_center_freq(tuner-freq_offset, 0) 82 | self.source.set_freq_corr(0, 0) 83 | self.source.set_dc_offset_mode(0, 0) 84 | self.source.set_iq_balance_mode(0, 0) 85 | self.source.set_gain_mode(False, 0) 86 | self.source.set_gain(gain, 0) 87 | self.source.set_if_gain(20, 0) 88 | self.source.set_bb_gain(20, 0) 89 | self.source.set_antenna("", 0) 90 | self.source.set_bandwidth(0, 0) 91 | elif(card == "usrp"): 92 | self.freq_offset=freq_offset=0; 93 | self.source = uhd.usrp_source( 94 | ",".join(("", "")), 95 | uhd.stream_args( 96 | cpu_format="fc32", 97 | channels=range(1), 98 | ), 99 | ) 100 | self.source.set_samp_rate(samp_rate) 101 | self.source.set_center_freq(tuner-freq_offset, 0) 102 | self.source.set_gain(gain, 0) 103 | else: 104 | print "Unrecognized device ", 105 | print card 106 | sys.exit(2); 107 | 108 | self.channel_xlating_filter = filter.freq_xlating_fir_filter_ccc( 109 | 1, (1, ), freq_offset, samp_rate); 110 | self.rational_resampler = filter.rational_resampler_ccc( 111 | interpolation=int(fm_sample_rate), decimation=int(samp_rate), 112 | taps=None, fractional_bw=None); 113 | self.fm_receiver = fmr.fm_receiver(audio_rate, fm_sample_rate); 114 | self.rds_sync = rds_sync.rds_synchronizer(); 115 | self.rds_dec = rds_dec.rds_decoder(); 116 | self.register_rds_handlers(); 117 | self.audio_sink = audio.sink(audio_rate, "", True) 118 | 119 | ################################################## 120 | # GUI blocks 121 | ################################################## 122 | #Tuner bar 123 | self.tuner_layout = Qt.QVBoxLayout() 124 | self.tuner_tool_bar = Qt.QToolBar(self) 125 | self.tuner_layout.addWidget(self.tuner_tool_bar) 126 | self.tuner_tool_bar.addWidget(Qt.QLabel("tuner"+": ")) 127 | class qwt_counter_pyslot(Qwt.QwtCounter): 128 | def __init__(self, parent=None): 129 | Qwt.QwtCounter.__init__(self, parent) 130 | @pyqtSlot('double') 131 | def setValue(self, value): 132 | super(Qwt.QwtCounter, self).setValue(value) 133 | self.tuner_counter = qwt_counter_pyslot() 134 | self.tuner_counter.setRange(fm_start, fm_stop, 0.1e6) 135 | self.tuner_counter.setNumButtons(2) 136 | self.tuner_counter.setValue(self.tuner) 137 | self.tuner_counter.valueChanged.connect(self.set_tuner) 138 | self.tuner_tool_bar.addWidget(self.tuner_counter) 139 | self.tuner_slider = Qwt.QwtSlider(None, Qt.Qt.Horizontal, 140 | Qwt.QwtSlider.BottomScale, Qwt.QwtSlider.BgSlot) 141 | self.tuner_slider.setRange(fm_start, fm_stop, 0.1e6) 142 | self.tuner_slider.setValue(self.tuner) 143 | self.tuner_slider.setMinimumWidth(200) 144 | self.tuner_slider.valueChanged.connect(self.set_tuner) 145 | self.tuner_layout.addWidget(self.tuner_slider) 146 | self.top_layout.addLayout(self.tuner_layout) 147 | 148 | #Gain chooser 149 | self.gain_options = rtlsdr_gains; 150 | self.gain_labels = map(str, self.gain_options) 151 | self.gain_tool_bar = Qt.QToolBar(self) 152 | self.gain_tool_bar.addWidget(Qt.QLabel("gain"+": ")) 153 | self.gain_combo_box = Qt.QComboBox() 154 | self.gain_tool_bar.addWidget(self.gain_combo_box) 155 | for label in self.gain_labels: self.gain_combo_box.addItem(label) 156 | self.gain_callback = lambda i: Qt.QMetaObject.invokeMethod( 157 | self.gain_combo_box, "setCurrentIndex", 158 | Qt.Q_ARG("int", self.gain_options.index(i))) 159 | self.gain_callback(self.gain) 160 | self.gain_combo_box.currentIndexChanged.connect( 161 | lambda i: self.set_gain(self.gain_options[i])) 162 | self.top_layout.addWidget(self.gain_tool_bar) 163 | 164 | #FFT 165 | self.qtgui_freq_sink = qtgui.freq_sink_c( 166 | 512, #size 167 | firdes.WIN_BLACKMAN_hARRIS, #wintype 168 | 0, #fc 169 | samp_rate, #bw 170 | "FM Spectrum", #name 171 | 1 #number of inputs 172 | ) 173 | self.qtgui_freq_sink.set_update_time(0.10) 174 | self.qtgui_freq_sink.set_y_axis(-140, 10) 175 | self.qtgui_freq_sink.set_trigger_mode(qtgui.TRIG_MODE_FREE, 0.0, 0, "") 176 | self.qtgui_freq_sink.enable_autoscale(False) 177 | self.qtgui_freq_sink.enable_grid(True) 178 | self.qtgui_freq_sink.set_fft_average(1.0) 179 | if complex == type(float()): 180 | self.qtgui_freq_sink.set_plot_pos_half(not True) 181 | self._qtgui_freq_sink_win = sip.wrapinstance(self.qtgui_freq_sink.pyqwidget(), Qt.QWidget) 182 | self.top_layout.addWidget(self._qtgui_freq_sink_win) 183 | 184 | #Stereo indicator 185 | self.qtgui_number_sink = qtgui.number_sink(gr.sizeof_char, 0, 186 | qtgui.NUM_GRAPH_NONE, 1) 187 | self.qtgui_number_sink.set_update_time(0.10) 188 | self.qtgui_number_sink.set_title("Stereo") 189 | self.qtgui_number_sink.set_min(0, 0) 190 | self.qtgui_number_sink.set_max(0, 1) 191 | self.qtgui_number_sink.enable_autoscale(False) 192 | self._qtgui_number_sink_win = sip.wrapinstance(self.qtgui_number_sink.pyqwidget(), Qt.QWidget) 193 | self.top_layout.addWidget(self._qtgui_number_sink_win) 194 | 195 | ################################################## 196 | # Connections 197 | ################################################## 198 | self.connect((self.source, 0), (self.channel_xlating_filter, 0)) 199 | self.connect((self.channel_xlating_filter, 0), (self.rational_resampler, 0)) 200 | self.connect((self.rational_resampler, 0), (self.fm_receiver, 0)) 201 | self.connect((self.fm_receiver, 0), (self.rds_sync, 0)) 202 | self.msg_connect(self.rds_sync, 'Group', self.rds_dec, 'Group') 203 | self.connect((self.fm_receiver, 1), (self.qtgui_number_sink, 0)) 204 | self.connect((self.fm_receiver, 2), (self.audio_sink, 1)) 205 | self.connect((self.fm_receiver, 3), (self.audio_sink, 0)) 206 | 207 | ################################################## 208 | # GUI connections 209 | ################################################## 210 | self.connect((self.channel_xlating_filter, 0), (self.qtgui_freq_sink, 0)) 211 | 212 | def get_tuner(self): 213 | return self.tuner 214 | 215 | def set_tuner(self, tuner): 216 | self.tuner = tuner 217 | self.source.set_center_freq(self.tuner-self.freq_offset, 0) 218 | Qt.QMetaObject.invokeMethod(self.tuner_counter, "setValue", Qt.Q_ARG("double", self.tuner)) 219 | Qt.QMetaObject.invokeMethod(self.tuner_slider, "setValue", Qt.Q_ARG("double", self.tuner)) 220 | self.rds_dec.reset_attr(); 221 | 222 | def get_samp_rate(self): 223 | return self.samp_rate 224 | 225 | def set_samp_rate(self, samp_rate): 226 | self.samp_rate = samp_rate 227 | self.source.set_sample_rate(self.samp_rate) 228 | self.channel_xlating_filter.set_taps((firdes.low_pass(1, self.samp_rate, 100e3, 100e3))) 229 | self.fm_receiver.set_samp_rate(self.samp_rate) 230 | 231 | def get_gain(self): 232 | return self.gain 233 | 234 | def set_gain(self, gain): 235 | self.gain = gain 236 | self.gain_callback(self.gain) 237 | self.source.set_gain(self.gain, 0) 238 | 239 | def get_audio_rate(self): 240 | return self.audio_rate 241 | 242 | def set_audio_rate(self, audio_rate): 243 | self.audio_rate = audio_rate 244 | self.qtgui_time_sink_x_1.set_samp_rate(self.audio_rate) 245 | self.fm_receiver.set_audio_rate(self.audio_rate) 246 | 247 | def closeEvent(self, event): 248 | self.settings = Qt.QSettings("GNU Radio", "top_block") 249 | self.settings.setValue("geometry", self.saveGeometry()) 250 | event.accept() 251 | 252 | def register_rds_handlers(self): 253 | handlers = rds_handlers.rds_handlers(); 254 | #synchronizer 255 | self.rds_sync.sync_event.add_handler(handlers.log_sync); 256 | #decoder 257 | self.rds_dec.ps_name_event.add_handler(handlers.print_ps_name); 258 | self.rds_dec.rt_event.add_handler(handlers.print_radiotext); 259 | #self.rds_dec.af_event.add_handler(handlers.print_af); 260 | self.rds_dec.date_event.add_handler(handlers.print_date); 261 | #self.rds_dec.eon_change_event.add_handler(handlers.print_eon_info); 262 | 263 | if __name__ == "__main__": 264 | helpstring='fm.py -c (rtlsdr|usrp) -s sample rate'; 265 | 266 | try: 267 | opts, args = getopt.getopt(sys.argv[1:],"hc:s:",["card=", "samp_rate="]); 268 | except getopt.GetoptError: 269 | print helpstring; 270 | sys.exit(2); 271 | 272 | for opt, arg in opts: 273 | if opt == '-h': 274 | print helpstring; 275 | sys.exit(); 276 | elif opt in ("-c", "--card"): 277 | card = arg; 278 | elif opt in ("-s", "--samp_rate"): 279 | samp_rate = arg; 280 | 281 | qapp = Qt.QApplication(sys.argv) 282 | 283 | receiver=radio(98.2e6, 29.7, card, int(samp_rate)); 284 | 285 | receiver.start(); 286 | receiver.show(); 287 | 288 | def quitting(): 289 | receiver.stop(); 290 | receiver.wait(); 291 | 292 | qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting) 293 | qapp.exec_() 294 | receiver = None #to clean up Qt widgets 295 | -------------------------------------------------------------------------------- /radio/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmrqt/fm-rds/9e53bff5b054c56ef45c9582f01b7bc85e26cb83/radio/__init__.py -------------------------------------------------------------------------------- /radio/diff_manchester.fsm: -------------------------------------------------------------------------------- 1 | 2 2 4 2 | 3 | 0 1 4 | 1 0 5 | 6 | 1 2 7 | 2 1 8 | 9 | FSM for the differential manchester code used in RDS. 10 | -------------------------------------------------------------------------------- /radio/fm_audio_demod.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################## 3 | # GNU Radio Python Flow Graph 4 | # Title: FM audio demodulator 5 | # Author: Alexandre Marquet 6 | # Generated: Fri Aug 25 15:48:40 2017 7 | ################################################## 8 | 9 | from gnuradio import analog 10 | from gnuradio import blocks 11 | from gnuradio import filter 12 | from gnuradio import gr 13 | from gnuradio.filter import firdes 14 | 15 | 16 | class fm_audio_demod(gr.hier_block2): 17 | 18 | def __init__(self, audio_rate=48000, samp_rate=512e3): 19 | gr.hier_block2.__init__( 20 | self, "FM audio demodulator", 21 | gr.io_signaturev(2, 2, [gr.sizeof_gr_complex*1, gr.sizeof_gr_complex*1]), 22 | gr.io_signaturev(3, 3, [gr.sizeof_char*1, gr.sizeof_float*1, gr.sizeof_float*1]), 23 | ) 24 | 25 | ################################################## 26 | # Parameters 27 | ################################################## 28 | self.audio_rate = audio_rate 29 | self.samp_rate = samp_rate 30 | 31 | ################################################## 32 | # Variables 33 | ################################################## 34 | self.thres_lo = thres_lo = 2 35 | self.thres_hi = thres_hi = 5 36 | self.avg_len = avg_len = int(1e6) 37 | 38 | ################################################## 39 | # Blocks 40 | ################################################## 41 | self.rational_resampler_xxx_0_0 = filter.rational_resampler_fff( 42 | interpolation=int(audio_rate), 43 | decimation=int(samp_rate), 44 | taps=None, 45 | fractional_bw=None, 46 | ) 47 | self.rational_resampler_xxx_0 = filter.rational_resampler_fff( 48 | interpolation=int(audio_rate), 49 | decimation=int(samp_rate), 50 | taps=None, 51 | fractional_bw=None, 52 | ) 53 | self.low_pass_filter_1_0 = filter.fir_filter_fff(1, firdes.low_pass( 54 | 1, audio_rate, 15e3, 1.5e3, firdes.WIN_HAMMING, 6.76)) 55 | self.low_pass_filter_1 = filter.fir_filter_fff(1, firdes.low_pass( 56 | 1, audio_rate, 15e3, 1.5e3, firdes.WIN_HAMMING, 6.76)) 57 | self.blocks_threshold_ff_0 = blocks.threshold_ff(thres_hi, thres_hi, 0) 58 | self.blocks_sub_xx_0 = blocks.sub_ff(1) 59 | self.blocks_multiply_xx_2 = blocks.multiply_vff(1) 60 | self.blocks_multiply_xx_1 = blocks.multiply_vff(1) 61 | self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) 62 | self.blocks_moving_average_xx_0_0 = blocks.moving_average_ff(avg_len, 1.0, 4000) 63 | self.blocks_moving_average_xx_0 = blocks.moving_average_ff(avg_len, 1.0, 4000) 64 | self.blocks_float_to_char_0 = blocks.float_to_char(1, 1) 65 | self.blocks_divide_xx_0 = blocks.divide_ff(1) 66 | self.blocks_complex_to_real_0_0 = blocks.complex_to_real(1) 67 | self.blocks_complex_to_real_0 = blocks.complex_to_real(1) 68 | self.blocks_add_xx_0 = blocks.add_vff(1) 69 | self.blocks_add_const_vxx_0 = blocks.add_const_vff((-1, )) 70 | self.blocks_abs_xx_1 = blocks.abs_ff(1) 71 | self.blocks_abs_xx_0_0 = blocks.abs_ff(1) 72 | self.blocks_abs_xx_0 = blocks.abs_ff(1) 73 | self.analog_fm_deemph_0_0_0_0 = analog.fm_deemph(fs=audio_rate, tau=50e-6) 74 | self.analog_fm_deemph_0_0_0 = analog.fm_deemph(fs=audio_rate, tau=50e-6) 75 | 76 | ################################################## 77 | # Connections 78 | ################################################## 79 | self.connect((self.analog_fm_deemph_0_0_0, 0), (self, 1)) 80 | self.connect((self.analog_fm_deemph_0_0_0_0, 0), (self, 2)) 81 | self.connect((self.blocks_abs_xx_0, 0), (self.blocks_moving_average_xx_0, 0)) 82 | self.connect((self.blocks_abs_xx_0_0, 0), (self.blocks_moving_average_xx_0_0, 0)) 83 | self.connect((self.blocks_abs_xx_1, 0), (self.blocks_float_to_char_0, 0)) 84 | self.connect((self.blocks_abs_xx_1, 0), (self.blocks_multiply_xx_1, 0)) 85 | self.connect((self.blocks_add_const_vxx_0, 0), (self.blocks_abs_xx_1, 0)) 86 | self.connect((self.blocks_add_xx_0, 0), (self.analog_fm_deemph_0_0_0, 0)) 87 | self.connect((self.blocks_complex_to_real_0, 0), (self.rational_resampler_xxx_0, 0)) 88 | self.connect((self.blocks_complex_to_real_0_0, 0), (self.rational_resampler_xxx_0_0, 0)) 89 | self.connect((self.blocks_divide_xx_0, 0), (self.blocks_multiply_xx_1, 1)) 90 | self.connect((self.blocks_divide_xx_0, 0), (self.blocks_threshold_ff_0, 0)) 91 | self.connect((self.blocks_float_to_char_0, 0), (self, 0)) 92 | self.connect((self.blocks_moving_average_xx_0, 0), (self.blocks_divide_xx_0, 0)) 93 | self.connect((self.blocks_moving_average_xx_0_0, 0), (self.blocks_divide_xx_0, 1)) 94 | self.connect((self.blocks_multiply_xx_0, 0), (self.blocks_complex_to_real_0_0, 0)) 95 | self.connect((self.blocks_multiply_xx_1, 0), (self.blocks_multiply_xx_2, 1)) 96 | self.connect((self.blocks_multiply_xx_2, 0), (self.blocks_add_xx_0, 1)) 97 | self.connect((self.blocks_multiply_xx_2, 0), (self.blocks_sub_xx_0, 1)) 98 | self.connect((self.blocks_sub_xx_0, 0), (self.analog_fm_deemph_0_0_0_0, 0)) 99 | self.connect((self.blocks_threshold_ff_0, 0), (self.blocks_add_const_vxx_0, 0)) 100 | self.connect((self.low_pass_filter_1, 0), (self.blocks_abs_xx_0_0, 0)) 101 | self.connect((self.low_pass_filter_1, 0), (self.blocks_multiply_xx_2, 0)) 102 | self.connect((self.low_pass_filter_1_0, 0), (self.blocks_abs_xx_0, 0)) 103 | self.connect((self.low_pass_filter_1_0, 0), (self.blocks_add_xx_0, 0)) 104 | self.connect((self.low_pass_filter_1_0, 0), (self.blocks_sub_xx_0, 0)) 105 | self.connect((self, 0), (self.blocks_complex_to_real_0, 0)) 106 | self.connect((self, 0), (self.blocks_multiply_xx_0, 0)) 107 | self.connect((self, 1), (self.blocks_multiply_xx_0, 1)) 108 | self.connect((self.rational_resampler_xxx_0, 0), (self.low_pass_filter_1_0, 0)) 109 | self.connect((self.rational_resampler_xxx_0_0, 0), (self.low_pass_filter_1, 0)) 110 | 111 | def get_audio_rate(self): 112 | return self.audio_rate 113 | 114 | def set_audio_rate(self, audio_rate): 115 | self.audio_rate = audio_rate 116 | self.low_pass_filter_1_0.set_taps(firdes.low_pass(1, self.audio_rate, 15e3, 1.5e3, firdes.WIN_HAMMING, 6.76)) 117 | self.low_pass_filter_1.set_taps(firdes.low_pass(1, self.audio_rate, 15e3, 1.5e3, firdes.WIN_HAMMING, 6.76)) 118 | 119 | def get_samp_rate(self): 120 | return self.samp_rate 121 | 122 | def set_samp_rate(self, samp_rate): 123 | self.samp_rate = samp_rate 124 | 125 | def get_thres_lo(self): 126 | return self.thres_lo 127 | 128 | def set_thres_lo(self, thres_lo): 129 | self.thres_lo = thres_lo 130 | 131 | def get_thres_hi(self): 132 | return self.thres_hi 133 | 134 | def set_thres_hi(self, thres_hi): 135 | self.thres_hi = thres_hi 136 | self.blocks_threshold_ff_0.set_hi(self.thres_hi) 137 | self.blocks_threshold_ff_0.set_lo(self.thres_hi) 138 | 139 | def get_avg_len(self): 140 | return self.avg_len 141 | 142 | def set_avg_len(self, avg_len): 143 | self.avg_len = avg_len 144 | self.blocks_moving_average_xx_0_0.set_length_and_scale(self.avg_len, 1.0) 145 | self.blocks_moving_average_xx_0.set_length_and_scale(self.avg_len, 1.0) 146 | -------------------------------------------------------------------------------- /radio/fm_receiver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################## 3 | # GNU Radio Python Flow Graph 4 | # Title: FM receiver 5 | # Author: Alexandre Marquet 6 | # Generated: Fri Aug 25 15:02:40 2017 7 | ################################################## 8 | 9 | import os 10 | import sys 11 | sys.path.append(os.environ.get('GRC_HIER_PATH', os.path.expanduser('~/.grc_gnuradio'))) 12 | 13 | from gnuradio import analog 14 | from gnuradio import blocks 15 | from gnuradio import gr 16 | from gnuradio.filter import firdes 17 | from mpx_demod import mpx_demod # grc-generated hier_block 18 | import math 19 | 20 | 21 | class fm_receiver(gr.hier_block2): 22 | 23 | def __init__(self, audio_rate=48000, samp_rate=512e3): 24 | gr.hier_block2.__init__( 25 | self, "FM receiver", 26 | gr.io_signature(1, 1, gr.sizeof_gr_complex*1), 27 | gr.io_signaturev(5, 5, [gr.sizeof_char*1, gr.sizeof_char*1, gr.sizeof_float*1, gr.sizeof_float*1, gr.sizeof_gr_complex*1]), 28 | ) 29 | 30 | ################################################## 31 | # Parameters 32 | ################################################## 33 | self.audio_rate = audio_rate 34 | self.samp_rate = samp_rate 35 | 36 | ################################################## 37 | # Blocks 38 | ################################################## 39 | self.mpx_demod_0 = mpx_demod( 40 | audio_rate=audio_rate, 41 | samp_rate=samp_rate, 42 | ) 43 | self.blocks_float_to_complex_0_0 = blocks.float_to_complex(1) 44 | self.analog_quadrature_demod_cf_0 = analog.quadrature_demod_cf(2*math.pi*75e3/samp_rate) 45 | 46 | ################################################## 47 | # Connections 48 | ################################################## 49 | self.connect((self.analog_quadrature_demod_cf_0, 0), (self.blocks_float_to_complex_0_0, 0)) 50 | self.connect((self.blocks_float_to_complex_0_0, 0), (self.mpx_demod_0, 0)) 51 | self.connect((self.mpx_demod_0, 0), (self, 0)) 52 | self.connect((self.mpx_demod_0, 1), (self, 1)) 53 | self.connect((self.mpx_demod_0, 2), (self, 2)) 54 | self.connect((self.mpx_demod_0, 3), (self, 3)) 55 | self.connect((self.mpx_demod_0, 4), (self, 4)) 56 | self.connect((self, 0), (self.analog_quadrature_demod_cf_0, 0)) 57 | 58 | def get_audio_rate(self): 59 | return self.audio_rate 60 | 61 | def set_audio_rate(self, audio_rate): 62 | self.audio_rate = audio_rate 63 | self.mpx_demod_0.set_audio_rate(self.audio_rate) 64 | 65 | def get_samp_rate(self): 66 | return self.samp_rate 67 | 68 | def set_samp_rate(self, samp_rate): 69 | self.samp_rate = samp_rate 70 | self.mpx_demod_0.set_samp_rate(self.samp_rate) 71 | self.analog_quadrature_demod_cf_0.set_gain(2*math.pi*75e3/self.samp_rate) 72 | -------------------------------------------------------------------------------- /radio/grc/.gitignore: -------------------------------------------------------------------------------- 1 | *.py 2 | -------------------------------------------------------------------------------- /radio/grc/diff_manchester.fsm: -------------------------------------------------------------------------------- 1 | 2 2 4 2 | 3 | 0 1 4 | 1 0 5 | 6 | 1 2 7 | 2 1 8 | 9 | FSM for the differential manchester code used in RDS. 10 | -------------------------------------------------------------------------------- /radio/grc/fm_audio_demod.grc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fri Dec 19 19:08:46 2014 5 | 6 | options 7 | 8 | author 9 | Alexandre Marquet 10 | 11 | 12 | window_size 13 | 1280, 1024 14 | 15 | 16 | category 17 | RDS 18 | 19 | 20 | comment 21 | 22 | 23 | 24 | description 25 | 26 | 27 | 28 | _enabled 29 | True 30 | 31 | 32 | _coordinate 33 | (10, 10) 34 | 35 | 36 | _rotation 37 | 0 38 | 39 | 40 | generate_options 41 | hb 42 | 43 | 44 | hier_block_src_path 45 | .: 46 | 47 | 48 | id 49 | fm_audio_demod 50 | 51 | 52 | max_nouts 53 | 0 54 | 55 | 56 | qt_qss_theme 57 | 58 | 59 | 60 | realtime_scheduling 61 | 62 | 63 | 64 | run_command 65 | {python} -u {filename} 66 | 67 | 68 | run_options 69 | prompt 70 | 71 | 72 | run 73 | True 74 | 75 | 76 | thread_safe_setters 77 | 78 | 79 | 80 | title 81 | FM audio demodulator 82 | 83 | 84 | 85 | variable 86 | 87 | comment 88 | 89 | 90 | 91 | _enabled 92 | True 93 | 94 | 95 | _coordinate 96 | (496, 12) 97 | 98 | 99 | _rotation 100 | 0 101 | 102 | 103 | id 104 | avg_len 105 | 106 | 107 | value 108 | int(1e6) 109 | 110 | 111 | 112 | variable 113 | 114 | comment 115 | 116 | 117 | 118 | _enabled 119 | True 120 | 121 | 122 | _coordinate 123 | (592, 12) 124 | 125 | 126 | _rotation 127 | 0 128 | 129 | 130 | id 131 | thres_hi 132 | 133 | 134 | value 135 | 5 136 | 137 | 138 | 139 | variable 140 | 141 | comment 142 | 143 | 144 | 145 | _enabled 146 | True 147 | 148 | 149 | _coordinate 150 | (680, 12) 151 | 152 | 153 | _rotation 154 | 0 155 | 156 | 157 | id 158 | thres_lo 159 | 160 | 161 | value 162 | 2 163 | 164 | 165 | 166 | analog_fm_deemph 167 | 168 | alias 169 | 170 | 171 | 172 | comment 173 | 174 | 175 | 176 | affinity 177 | 178 | 179 | 180 | _enabled 181 | 1 182 | 183 | 184 | _coordinate 185 | (696, 820) 186 | 187 | 188 | _rotation 189 | 0 190 | 191 | 192 | id 193 | analog_fm_deemph_0_0_0 194 | 195 | 196 | maxoutbuf 197 | 0 198 | 199 | 200 | minoutbuf 201 | 0 202 | 203 | 204 | samp_rate 205 | audio_rate 206 | 207 | 208 | tau 209 | 50e-6 210 | 211 | 212 | 213 | analog_fm_deemph 214 | 215 | alias 216 | 217 | 218 | 219 | comment 220 | 221 | 222 | 223 | affinity 224 | 225 | 226 | 227 | _enabled 228 | 1 229 | 230 | 231 | _coordinate 232 | (696, 916) 233 | 234 | 235 | _rotation 236 | 0 237 | 238 | 239 | id 240 | analog_fm_deemph_0_0_0_0 241 | 242 | 243 | maxoutbuf 244 | 0 245 | 246 | 247 | minoutbuf 248 | 0 249 | 250 | 251 | samp_rate 252 | audio_rate 253 | 254 | 255 | tau 256 | 50e-6 257 | 258 | 259 | 260 | parameter 261 | 262 | alias 263 | 264 | 265 | 266 | comment 267 | 268 | 269 | 270 | _enabled 271 | True 272 | 273 | 274 | _coordinate 275 | (368, 12) 276 | 277 | 278 | _rotation 279 | 0 280 | 281 | 282 | id 283 | audio_rate 284 | 285 | 286 | label 287 | Audio rate 288 | 289 | 290 | short_id 291 | 292 | 293 | 294 | type 295 | eng_float 296 | 297 | 298 | value 299 | 48000 300 | 301 | 302 | 303 | blocks_abs_xx 304 | 305 | alias 306 | 307 | 308 | 309 | comment 310 | 311 | 312 | 313 | affinity 314 | 315 | 316 | 317 | _enabled 318 | 1 319 | 320 | 321 | _coordinate 322 | (208, 560) 323 | 324 | 325 | _rotation 326 | 0 327 | 328 | 329 | id 330 | blocks_abs_xx_0 331 | 332 | 333 | type 334 | float 335 | 336 | 337 | maxoutbuf 338 | 0 339 | 340 | 341 | minoutbuf 342 | 0 343 | 344 | 345 | vlen 346 | 1 347 | 348 | 349 | 350 | blocks_abs_xx 351 | 352 | alias 353 | 354 | 355 | 356 | comment 357 | 358 | 359 | 360 | affinity 361 | 362 | 363 | 364 | _enabled 365 | 1 366 | 367 | 368 | _coordinate 369 | (208, 688) 370 | 371 | 372 | _rotation 373 | 0 374 | 375 | 376 | id 377 | blocks_abs_xx_0_0 378 | 379 | 380 | type 381 | float 382 | 383 | 384 | maxoutbuf 385 | 0 386 | 387 | 388 | minoutbuf 389 | 0 390 | 391 | 392 | vlen 393 | 1 394 | 395 | 396 | 397 | blocks_abs_xx 398 | 399 | alias 400 | 401 | 402 | 403 | comment 404 | 405 | 406 | 407 | affinity 408 | 409 | 410 | 411 | _enabled 412 | True 413 | 414 | 415 | _coordinate 416 | (856, 512) 417 | 418 | 419 | _rotation 420 | 0 421 | 422 | 423 | id 424 | blocks_abs_xx_1 425 | 426 | 427 | type 428 | float 429 | 430 | 431 | maxoutbuf 432 | 0 433 | 434 | 435 | minoutbuf 436 | 0 437 | 438 | 439 | vlen 440 | 1 441 | 442 | 443 | 444 | blocks_add_const_vxx 445 | 446 | alias 447 | 448 | 449 | 450 | comment 451 | 452 | 453 | 454 | const 455 | -1 456 | 457 | 458 | affinity 459 | 460 | 461 | 462 | _enabled 463 | True 464 | 465 | 466 | _coordinate 467 | (736, 508) 468 | 469 | 470 | _rotation 471 | 0 472 | 473 | 474 | id 475 | blocks_add_const_vxx_0 476 | 477 | 478 | type 479 | float 480 | 481 | 482 | maxoutbuf 483 | 0 484 | 485 | 486 | minoutbuf 487 | 0 488 | 489 | 490 | vlen 491 | 1 492 | 493 | 494 | 495 | blocks_add_xx 496 | 497 | alias 498 | 499 | 500 | 501 | comment 502 | 503 | 504 | 505 | affinity 506 | 507 | 508 | 509 | _enabled 510 | 1 511 | 512 | 513 | _coordinate 514 | (584, 816) 515 | 516 | 517 | _rotation 518 | 0 519 | 520 | 521 | id 522 | blocks_add_xx_0 523 | 524 | 525 | type 526 | float 527 | 528 | 529 | maxoutbuf 530 | 0 531 | 532 | 533 | minoutbuf 534 | 0 535 | 536 | 537 | num_inputs 538 | 2 539 | 540 | 541 | vlen 542 | 1 543 | 544 | 545 | 546 | blocks_complex_to_real 547 | 548 | alias 549 | 550 | 551 | 552 | comment 553 | 554 | 555 | 556 | affinity 557 | 558 | 559 | 560 | _enabled 561 | 1 562 | 563 | 564 | _coordinate 565 | (480, 192) 566 | 567 | 568 | _rotation 569 | 0 570 | 571 | 572 | id 573 | blocks_complex_to_real_0 574 | 575 | 576 | maxoutbuf 577 | 0 578 | 579 | 580 | minoutbuf 581 | 0 582 | 583 | 584 | vlen 585 | 1 586 | 587 | 588 | 589 | blocks_complex_to_real 590 | 591 | alias 592 | 593 | 594 | 595 | comment 596 | 597 | 598 | 599 | affinity 600 | 601 | 602 | 603 | _enabled 604 | 1 605 | 606 | 607 | _coordinate 608 | (480, 344) 609 | 610 | 611 | _rotation 612 | 0 613 | 614 | 615 | id 616 | blocks_complex_to_real_0_0 617 | 618 | 619 | maxoutbuf 620 | 0 621 | 622 | 623 | minoutbuf 624 | 0 625 | 626 | 627 | vlen 628 | 1 629 | 630 | 631 | 632 | blocks_divide_xx 633 | 634 | alias 635 | 636 | 637 | 638 | comment 639 | 640 | 641 | 642 | affinity 643 | 644 | 645 | 646 | _enabled 647 | 1 648 | 649 | 650 | _coordinate 651 | (480, 616) 652 | 653 | 654 | _rotation 655 | 0 656 | 657 | 658 | id 659 | blocks_divide_xx_0 660 | 661 | 662 | type 663 | float 664 | 665 | 666 | maxoutbuf 667 | 0 668 | 669 | 670 | minoutbuf 671 | 0 672 | 673 | 674 | num_inputs 675 | 2 676 | 677 | 678 | vlen 679 | 1 680 | 681 | 682 | 683 | blocks_float_to_char 684 | 685 | alias 686 | 687 | 688 | 689 | comment 690 | 691 | 692 | 693 | affinity 694 | 695 | 696 | 697 | _enabled 698 | True 699 | 700 | 701 | _coordinate 702 | (1040, 508) 703 | 704 | 705 | _rotation 706 | 0 707 | 708 | 709 | id 710 | blocks_float_to_char_0 711 | 712 | 713 | maxoutbuf 714 | 0 715 | 716 | 717 | minoutbuf 718 | 0 719 | 720 | 721 | scale 722 | 1 723 | 724 | 725 | vlen 726 | 1 727 | 728 | 729 | 730 | blocks_moving_average_xx 731 | 732 | alias 733 | 734 | 735 | 736 | comment 737 | 738 | 739 | 740 | affinity 741 | 742 | 743 | 744 | _enabled 745 | 1 746 | 747 | 748 | _coordinate 749 | (296, 540) 750 | 751 | 752 | _rotation 753 | 0 754 | 755 | 756 | id 757 | blocks_moving_average_xx_0 758 | 759 | 760 | length 761 | avg_len 762 | 763 | 764 | max_iter 765 | 4000 766 | 767 | 768 | maxoutbuf 769 | 0 770 | 771 | 772 | minoutbuf 773 | 0 774 | 775 | 776 | scale 777 | 1.0 778 | 779 | 780 | type 781 | float 782 | 783 | 784 | 785 | blocks_moving_average_xx 786 | 787 | alias 788 | 789 | 790 | 791 | comment 792 | 793 | 794 | 795 | affinity 796 | 797 | 798 | 799 | _enabled 800 | 1 801 | 802 | 803 | _coordinate 804 | (296, 668) 805 | 806 | 807 | _rotation 808 | 0 809 | 810 | 811 | id 812 | blocks_moving_average_xx_0_0 813 | 814 | 815 | length 816 | avg_len 817 | 818 | 819 | max_iter 820 | 4000 821 | 822 | 823 | maxoutbuf 824 | 0 825 | 826 | 827 | minoutbuf 828 | 0 829 | 830 | 831 | scale 832 | 1.0 833 | 834 | 835 | type 836 | float 837 | 838 | 839 | 840 | blocks_multiply_xx 841 | 842 | alias 843 | 844 | 845 | 846 | comment 847 | 848 | 849 | 850 | affinity 851 | 852 | 853 | 854 | _enabled 855 | True 856 | 857 | 858 | _coordinate 859 | (184, 328) 860 | 861 | 862 | _rotation 863 | 0 864 | 865 | 866 | id 867 | blocks_multiply_xx_0 868 | 869 | 870 | type 871 | complex 872 | 873 | 874 | maxoutbuf 875 | 0 876 | 877 | 878 | minoutbuf 879 | 0 880 | 881 | 882 | num_inputs 883 | 2 884 | 885 | 886 | vlen 887 | 1 888 | 889 | 890 | 891 | blocks_multiply_xx 892 | 893 | alias 894 | 895 | 896 | 897 | comment 898 | 899 | 900 | 901 | affinity 902 | 903 | 904 | 905 | _enabled 906 | True 907 | 908 | 909 | _coordinate 910 | (984, 600) 911 | 912 | 913 | _rotation 914 | 0 915 | 916 | 917 | id 918 | blocks_multiply_xx_1 919 | 920 | 921 | type 922 | float 923 | 924 | 925 | maxoutbuf 926 | 0 927 | 928 | 929 | minoutbuf 930 | 0 931 | 932 | 933 | num_inputs 934 | 2 935 | 936 | 937 | vlen 938 | 1 939 | 940 | 941 | 942 | blocks_multiply_xx 943 | 944 | alias 945 | 946 | 947 | 948 | comment 949 | 950 | 951 | 952 | affinity 953 | 954 | 955 | 956 | _enabled 957 | True 958 | 959 | 960 | _coordinate 961 | (320, 912) 962 | 963 | 964 | _rotation 965 | 0 966 | 967 | 968 | id 969 | blocks_multiply_xx_2 970 | 971 | 972 | type 973 | float 974 | 975 | 976 | maxoutbuf 977 | 0 978 | 979 | 980 | minoutbuf 981 | 0 982 | 983 | 984 | num_inputs 985 | 2 986 | 987 | 988 | vlen 989 | 1 990 | 991 | 992 | 993 | blocks_sub_xx 994 | 995 | alias 996 | 997 | 998 | 999 | comment 1000 | 1001 | 1002 | 1003 | affinity 1004 | 1005 | 1006 | 1007 | _enabled 1008 | 1 1009 | 1010 | 1011 | _coordinate 1012 | (584, 912) 1013 | 1014 | 1015 | _rotation 1016 | 0 1017 | 1018 | 1019 | id 1020 | blocks_sub_xx_0 1021 | 1022 | 1023 | type 1024 | float 1025 | 1026 | 1027 | maxoutbuf 1028 | 0 1029 | 1030 | 1031 | minoutbuf 1032 | 0 1033 | 1034 | 1035 | num_inputs 1036 | 2 1037 | 1038 | 1039 | vlen 1040 | 1 1041 | 1042 | 1043 | 1044 | blocks_threshold_ff 1045 | 1046 | alias 1047 | 1048 | 1049 | 1050 | comment 1051 | 1052 | 1053 | 1054 | affinity 1055 | 1056 | 1057 | 1058 | _enabled 1059 | True 1060 | 1061 | 1062 | _coordinate 1063 | (600, 492) 1064 | 1065 | 1066 | _rotation 1067 | 0 1068 | 1069 | 1070 | high 1071 | thres_hi 1072 | 1073 | 1074 | id 1075 | blocks_threshold_ff_0 1076 | 1077 | 1078 | init 1079 | 0 1080 | 1081 | 1082 | low 1083 | thres_hi 1084 | 1085 | 1086 | maxoutbuf 1087 | 0 1088 | 1089 | 1090 | minoutbuf 1091 | 0 1092 | 1093 | 1094 | 1095 | low_pass_filter 1096 | 1097 | beta 1098 | 6.76 1099 | 1100 | 1101 | alias 1102 | 1103 | 1104 | 1105 | comment 1106 | 1107 | 1108 | 1109 | affinity 1110 | 1111 | 1112 | 1113 | cutoff_freq 1114 | 15e3 1115 | 1116 | 1117 | decim 1118 | 1 1119 | 1120 | 1121 | _enabled 1122 | 1 1123 | 1124 | 1125 | type 1126 | fir_filter_fff 1127 | 1128 | 1129 | _coordinate 1130 | (848, 292) 1131 | 1132 | 1133 | _rotation 1134 | 0 1135 | 1136 | 1137 | gain 1138 | 1 1139 | 1140 | 1141 | id 1142 | low_pass_filter_1 1143 | 1144 | 1145 | interp 1146 | 1 1147 | 1148 | 1149 | maxoutbuf 1150 | 0 1151 | 1152 | 1153 | minoutbuf 1154 | 0 1155 | 1156 | 1157 | samp_rate 1158 | audio_rate 1159 | 1160 | 1161 | width 1162 | 1.5e3 1163 | 1164 | 1165 | win 1166 | firdes.WIN_HAMMING 1167 | 1168 | 1169 | 1170 | low_pass_filter 1171 | 1172 | beta 1173 | 6.76 1174 | 1175 | 1176 | alias 1177 | 1178 | 1179 | 1180 | comment 1181 | 1182 | 1183 | 1184 | affinity 1185 | 1186 | 1187 | 1188 | cutoff_freq 1189 | 15e3 1190 | 1191 | 1192 | decim 1193 | 1 1194 | 1195 | 1196 | _enabled 1197 | 1 1198 | 1199 | 1200 | type 1201 | fir_filter_fff 1202 | 1203 | 1204 | _coordinate 1205 | (848, 140) 1206 | 1207 | 1208 | _rotation 1209 | 0 1210 | 1211 | 1212 | gain 1213 | 1 1214 | 1215 | 1216 | id 1217 | low_pass_filter_1_0 1218 | 1219 | 1220 | interp 1221 | 1 1222 | 1223 | 1224 | maxoutbuf 1225 | 0 1226 | 1227 | 1228 | minoutbuf 1229 | 0 1230 | 1231 | 1232 | samp_rate 1233 | audio_rate 1234 | 1235 | 1236 | width 1237 | 1.5e3 1238 | 1239 | 1240 | win 1241 | firdes.WIN_HAMMING 1242 | 1243 | 1244 | 1245 | note 1246 | 1247 | alias 1248 | 1249 | 1250 | 1251 | comment 1252 | 1253 | 1254 | 1255 | _enabled 1256 | True 1257 | 1258 | 1259 | _coordinate 1260 | (680, 572) 1261 | 1262 | 1263 | _rotation 1264 | 0 1265 | 1266 | 1267 | id 1268 | note_0 1269 | 1270 | 1271 | note 1272 | Deactivate stereo mode of Power(L+R) >> Power(L-R) 1273 | 1274 | 1275 | 1276 | pad_sink 1277 | 1278 | comment 1279 | 1280 | 1281 | 1282 | _enabled 1283 | True 1284 | 1285 | 1286 | _coordinate 1287 | (1192, 508) 1288 | 1289 | 1290 | _rotation 1291 | 0 1292 | 1293 | 1294 | id 1295 | pad_sink_0 1296 | 1297 | 1298 | type 1299 | byte 1300 | 1301 | 1302 | label 1303 | stereo 1304 | 1305 | 1306 | num_streams 1307 | 1 1308 | 1309 | 1310 | optional 1311 | True 1312 | 1313 | 1314 | vlen 1315 | 1 1316 | 1317 | 1318 | 1319 | pad_sink 1320 | 1321 | comment 1322 | 1323 | 1324 | 1325 | _enabled 1326 | True 1327 | 1328 | 1329 | _coordinate 1330 | (864, 828) 1331 | 1332 | 1333 | _rotation 1334 | 0 1335 | 1336 | 1337 | id 1338 | pad_sink_1 1339 | 1340 | 1341 | type 1342 | float 1343 | 1344 | 1345 | label 1346 | L 1347 | 1348 | 1349 | num_streams 1350 | 1 1351 | 1352 | 1353 | optional 1354 | False 1355 | 1356 | 1357 | vlen 1358 | 1 1359 | 1360 | 1361 | 1362 | pad_sink 1363 | 1364 | comment 1365 | 1366 | 1367 | 1368 | _enabled 1369 | True 1370 | 1371 | 1372 | _coordinate 1373 | (864, 924) 1374 | 1375 | 1376 | _rotation 1377 | 0 1378 | 1379 | 1380 | id 1381 | pad_sink_2 1382 | 1383 | 1384 | type 1385 | float 1386 | 1387 | 1388 | label 1389 | R 1390 | 1391 | 1392 | num_streams 1393 | 1 1394 | 1395 | 1396 | optional 1397 | False 1398 | 1399 | 1400 | vlen 1401 | 1 1402 | 1403 | 1404 | 1405 | pad_source 1406 | 1407 | comment 1408 | 1409 | 1410 | 1411 | _enabled 1412 | True 1413 | 1414 | 1415 | _coordinate 1416 | (24, 188) 1417 | 1418 | 1419 | _rotation 1420 | 0 1421 | 1422 | 1423 | id 1424 | pad_source_0 1425 | 1426 | 1427 | label 1428 | MPX 1429 | 1430 | 1431 | num_streams 1432 | 1 1433 | 1434 | 1435 | optional 1436 | False 1437 | 1438 | 1439 | type 1440 | complex 1441 | 1442 | 1443 | vlen 1444 | 1 1445 | 1446 | 1447 | 1448 | pad_source 1449 | 1450 | comment 1451 | 1452 | 1453 | 1454 | _enabled 1455 | True 1456 | 1457 | 1458 | _coordinate 1459 | (24, 356) 1460 | 1461 | 1462 | _rotation 1463 | 0 1464 | 1465 | 1466 | id 1467 | pad_source_0_0 1468 | 1469 | 1470 | label 1471 | L-R carrier 1472 | 1473 | 1474 | num_streams 1475 | 1 1476 | 1477 | 1478 | optional 1479 | False 1480 | 1481 | 1482 | type 1483 | complex 1484 | 1485 | 1486 | vlen 1487 | 1 1488 | 1489 | 1490 | 1491 | rational_resampler_xxx 1492 | 1493 | alias 1494 | 1495 | 1496 | 1497 | comment 1498 | 1499 | 1500 | 1501 | affinity 1502 | 1503 | 1504 | 1505 | decim 1506 | int(samp_rate) 1507 | 1508 | 1509 | _enabled 1510 | 1 1511 | 1512 | 1513 | fbw 1514 | 0 1515 | 1516 | 1517 | _coordinate 1518 | (672, 164) 1519 | 1520 | 1521 | _rotation 1522 | 0 1523 | 1524 | 1525 | id 1526 | rational_resampler_xxx_0 1527 | 1528 | 1529 | interp 1530 | int(audio_rate) 1531 | 1532 | 1533 | maxoutbuf 1534 | 0 1535 | 1536 | 1537 | minoutbuf 1538 | 0 1539 | 1540 | 1541 | taps 1542 | 1543 | 1544 | 1545 | type 1546 | fff 1547 | 1548 | 1549 | 1550 | rational_resampler_xxx 1551 | 1552 | alias 1553 | 1554 | 1555 | 1556 | comment 1557 | 1558 | 1559 | 1560 | affinity 1561 | 1562 | 1563 | 1564 | decim 1565 | int(samp_rate) 1566 | 1567 | 1568 | _enabled 1569 | 1 1570 | 1571 | 1572 | fbw 1573 | 0 1574 | 1575 | 1576 | _coordinate 1577 | (672, 316) 1578 | 1579 | 1580 | _rotation 1581 | 0 1582 | 1583 | 1584 | id 1585 | rational_resampler_xxx_0_0 1586 | 1587 | 1588 | interp 1589 | int(audio_rate) 1590 | 1591 | 1592 | maxoutbuf 1593 | 0 1594 | 1595 | 1596 | minoutbuf 1597 | 0 1598 | 1599 | 1600 | taps 1601 | 1602 | 1603 | 1604 | type 1605 | fff 1606 | 1607 | 1608 | 1609 | parameter 1610 | 1611 | alias 1612 | 1613 | 1614 | 1615 | comment 1616 | 1617 | 1618 | 1619 | _enabled 1620 | True 1621 | 1622 | 1623 | _coordinate 1624 | (224, 11) 1625 | 1626 | 1627 | _rotation 1628 | 0 1629 | 1630 | 1631 | id 1632 | samp_rate 1633 | 1634 | 1635 | label 1636 | Sample rate 1637 | 1638 | 1639 | short_id 1640 | 1641 | 1642 | 1643 | type 1644 | eng_float 1645 | 1646 | 1647 | value 1648 | 512e3 1649 | 1650 | 1651 | 1652 | virtual_sink 1653 | 1654 | comment 1655 | 1656 | 1657 | 1658 | _enabled 1659 | True 1660 | 1661 | 1662 | _coordinate 1663 | (1056, 188) 1664 | 1665 | 1666 | _rotation 1667 | 0 1668 | 1669 | 1670 | id 1671 | virtual_sink_0 1672 | 1673 | 1674 | stream_id 1675 | LpR_comp 1676 | 1677 | 1678 | 1679 | virtual_sink 1680 | 1681 | comment 1682 | 1683 | 1684 | 1685 | _enabled 1686 | True 1687 | 1688 | 1689 | _coordinate 1690 | (1040, 340) 1691 | 1692 | 1693 | _rotation 1694 | 0 1695 | 1696 | 1697 | id 1698 | virtual_sink_0_0 1699 | 1700 | 1701 | stream_id 1702 | LmR_comp 1703 | 1704 | 1705 | 1706 | virtual_sink 1707 | 1708 | comment 1709 | 1710 | 1711 | 1712 | _enabled 1713 | True 1714 | 1715 | 1716 | _coordinate 1717 | (1120, 612) 1718 | 1719 | 1720 | _rotation 1721 | 0 1722 | 1723 | 1724 | id 1725 | virtual_sink_0_0_0 1726 | 1727 | 1728 | stream_id 1729 | LmR_gain 1730 | 1731 | 1732 | 1733 | virtual_source 1734 | 1735 | comment 1736 | 1737 | 1738 | 1739 | _enabled 1740 | True 1741 | 1742 | 1743 | _coordinate 1744 | (24, 556) 1745 | 1746 | 1747 | _rotation 1748 | 0 1749 | 1750 | 1751 | id 1752 | virtual_source_0 1753 | 1754 | 1755 | stream_id 1756 | LpR_comp 1757 | 1758 | 1759 | 1760 | virtual_source 1761 | 1762 | comment 1763 | 1764 | 1765 | 1766 | _enabled 1767 | True 1768 | 1769 | 1770 | _coordinate 1771 | (24, 684) 1772 | 1773 | 1774 | _rotation 1775 | 0 1776 | 1777 | 1778 | id 1779 | virtual_source_0_0 1780 | 1781 | 1782 | stream_id 1783 | LmR_comp 1784 | 1785 | 1786 | 1787 | virtual_source 1788 | 1789 | comment 1790 | 1791 | 1792 | 1793 | _enabled 1794 | True 1795 | 1796 | 1797 | _coordinate 1798 | (296, 812) 1799 | 1800 | 1801 | _rotation 1802 | 0 1803 | 1804 | 1805 | id 1806 | virtual_source_0_1 1807 | 1808 | 1809 | stream_id 1810 | LpR_comp 1811 | 1812 | 1813 | 1814 | virtual_source 1815 | 1816 | comment 1817 | 1818 | 1819 | 1820 | _enabled 1821 | True 1822 | 1823 | 1824 | _coordinate 1825 | (80, 884) 1826 | 1827 | 1828 | _rotation 1829 | 0 1830 | 1831 | 1832 | id 1833 | virtual_source_0_1_0 1834 | 1835 | 1836 | stream_id 1837 | LmR_comp 1838 | 1839 | 1840 | 1841 | virtual_source 1842 | 1843 | comment 1844 | 1845 | 1846 | 1847 | _enabled 1848 | True 1849 | 1850 | 1851 | _coordinate 1852 | (80, 940) 1853 | 1854 | 1855 | _rotation 1856 | 0 1857 | 1858 | 1859 | id 1860 | virtual_source_0_1_0_0 1861 | 1862 | 1863 | stream_id 1864 | LmR_gain 1865 | 1866 | 1867 | 1868 | analog_fm_deemph_0_0_0 1869 | pad_sink_1 1870 | 0 1871 | 0 1872 | 1873 | 1874 | analog_fm_deemph_0_0_0_0 1875 | pad_sink_2 1876 | 0 1877 | 0 1878 | 1879 | 1880 | blocks_abs_xx_0 1881 | blocks_moving_average_xx_0 1882 | 0 1883 | 0 1884 | 1885 | 1886 | blocks_abs_xx_0_0 1887 | blocks_moving_average_xx_0_0 1888 | 0 1889 | 0 1890 | 1891 | 1892 | blocks_abs_xx_1 1893 | blocks_float_to_char_0 1894 | 0 1895 | 0 1896 | 1897 | 1898 | blocks_abs_xx_1 1899 | blocks_multiply_xx_1 1900 | 0 1901 | 0 1902 | 1903 | 1904 | blocks_add_const_vxx_0 1905 | blocks_abs_xx_1 1906 | 0 1907 | 0 1908 | 1909 | 1910 | blocks_add_xx_0 1911 | analog_fm_deemph_0_0_0 1912 | 0 1913 | 0 1914 | 1915 | 1916 | blocks_complex_to_real_0 1917 | rational_resampler_xxx_0 1918 | 0 1919 | 0 1920 | 1921 | 1922 | blocks_complex_to_real_0_0 1923 | rational_resampler_xxx_0_0 1924 | 0 1925 | 0 1926 | 1927 | 1928 | blocks_divide_xx_0 1929 | blocks_multiply_xx_1 1930 | 0 1931 | 1 1932 | 1933 | 1934 | blocks_divide_xx_0 1935 | blocks_threshold_ff_0 1936 | 0 1937 | 0 1938 | 1939 | 1940 | blocks_float_to_char_0 1941 | pad_sink_0 1942 | 0 1943 | 0 1944 | 1945 | 1946 | blocks_moving_average_xx_0 1947 | blocks_divide_xx_0 1948 | 0 1949 | 0 1950 | 1951 | 1952 | blocks_moving_average_xx_0_0 1953 | blocks_divide_xx_0 1954 | 0 1955 | 1 1956 | 1957 | 1958 | blocks_multiply_xx_0 1959 | blocks_complex_to_real_0_0 1960 | 0 1961 | 0 1962 | 1963 | 1964 | blocks_multiply_xx_1 1965 | virtual_sink_0_0_0 1966 | 0 1967 | 0 1968 | 1969 | 1970 | blocks_multiply_xx_2 1971 | blocks_add_xx_0 1972 | 0 1973 | 1 1974 | 1975 | 1976 | blocks_multiply_xx_2 1977 | blocks_sub_xx_0 1978 | 0 1979 | 1 1980 | 1981 | 1982 | blocks_sub_xx_0 1983 | analog_fm_deemph_0_0_0_0 1984 | 0 1985 | 0 1986 | 1987 | 1988 | blocks_threshold_ff_0 1989 | blocks_add_const_vxx_0 1990 | 0 1991 | 0 1992 | 1993 | 1994 | low_pass_filter_1 1995 | virtual_sink_0_0 1996 | 0 1997 | 0 1998 | 1999 | 2000 | low_pass_filter_1_0 2001 | virtual_sink_0 2002 | 0 2003 | 0 2004 | 2005 | 2006 | pad_source_0 2007 | blocks_complex_to_real_0 2008 | 0 2009 | 0 2010 | 2011 | 2012 | pad_source_0 2013 | blocks_multiply_xx_0 2014 | 0 2015 | 0 2016 | 2017 | 2018 | pad_source_0_0 2019 | blocks_multiply_xx_0 2020 | 0 2021 | 1 2022 | 2023 | 2024 | rational_resampler_xxx_0 2025 | low_pass_filter_1_0 2026 | 0 2027 | 0 2028 | 2029 | 2030 | rational_resampler_xxx_0_0 2031 | low_pass_filter_1 2032 | 0 2033 | 0 2034 | 2035 | 2036 | virtual_source_0 2037 | blocks_abs_xx_0 2038 | 0 2039 | 0 2040 | 2041 | 2042 | virtual_source_0_0 2043 | blocks_abs_xx_0_0 2044 | 0 2045 | 0 2046 | 2047 | 2048 | virtual_source_0_1 2049 | blocks_add_xx_0 2050 | 0 2051 | 0 2052 | 2053 | 2054 | virtual_source_0_1 2055 | blocks_sub_xx_0 2056 | 0 2057 | 0 2058 | 2059 | 2060 | virtual_source_0_1_0 2061 | blocks_multiply_xx_2 2062 | 0 2063 | 0 2064 | 2065 | 2066 | virtual_source_0_1_0_0 2067 | blocks_multiply_xx_2 2068 | 0 2069 | 1 2070 | 2071 | 2072 | -------------------------------------------------------------------------------- /radio/grc/fm_receiver.grc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mon Oct 20 21:49:07 2014 5 | 6 | options 7 | 8 | author 9 | Alexandre Marquet 10 | 11 | 12 | window_size 13 | 2048, 1024 14 | 15 | 16 | category 17 | RDS 18 | 19 | 20 | comment 21 | 22 | 23 | 24 | description 25 | 26 | 27 | 28 | _enabled 29 | True 30 | 31 | 32 | _coordinate 33 | (10, 10) 34 | 35 | 36 | _rotation 37 | 0 38 | 39 | 40 | generate_options 41 | hb 42 | 43 | 44 | hier_block_src_path 45 | .: 46 | 47 | 48 | id 49 | fm_receiver 50 | 51 | 52 | max_nouts 53 | 0 54 | 55 | 56 | qt_qss_theme 57 | 58 | 59 | 60 | realtime_scheduling 61 | 62 | 63 | 64 | run_command 65 | {python} -u {filename} 66 | 67 | 68 | run_options 69 | prompt 70 | 71 | 72 | run 73 | True 74 | 75 | 76 | thread_safe_setters 77 | 78 | 79 | 80 | title 81 | FM receiver 82 | 83 | 84 | 85 | analog_quadrature_demod_cf 86 | 87 | alias 88 | 89 | 90 | 91 | comment 92 | 93 | 94 | 95 | affinity 96 | 97 | 98 | 99 | _enabled 100 | True 101 | 102 | 103 | _coordinate 104 | (208, 180) 105 | 106 | 107 | _rotation 108 | 0 109 | 110 | 111 | gain 112 | 2*math.pi*75e3/samp_rate 113 | 114 | 115 | id 116 | analog_quadrature_demod_cf_0 117 | 118 | 119 | maxoutbuf 120 | 0 121 | 122 | 123 | minoutbuf 124 | 0 125 | 126 | 127 | 128 | parameter 129 | 130 | alias 131 | 132 | 133 | 134 | comment 135 | 136 | 137 | 138 | _enabled 139 | True 140 | 141 | 142 | _coordinate 143 | (536, 11) 144 | 145 | 146 | _rotation 147 | 0 148 | 149 | 150 | id 151 | audio_rate 152 | 153 | 154 | label 155 | Audio rate 156 | 157 | 158 | short_id 159 | 160 | 161 | 162 | type 163 | eng_float 164 | 165 | 166 | value 167 | 48000 168 | 169 | 170 | 171 | blocks_float_to_complex 172 | 173 | alias 174 | 175 | 176 | 177 | comment 178 | 179 | 180 | 181 | affinity 182 | 183 | 184 | 185 | _enabled 186 | 1 187 | 188 | 189 | _coordinate 190 | (424, 184) 191 | 192 | 193 | _rotation 194 | 0 195 | 196 | 197 | id 198 | blocks_float_to_complex_0_0 199 | 200 | 201 | maxoutbuf 202 | 0 203 | 204 | 205 | minoutbuf 206 | 0 207 | 208 | 209 | vlen 210 | 1 211 | 212 | 213 | 214 | mpx_demod 215 | 216 | audio_rate 217 | audio_rate 218 | 219 | 220 | alias 221 | 222 | 223 | 224 | comment 225 | 226 | 227 | 228 | affinity 229 | 230 | 231 | 232 | _enabled 233 | 1 234 | 235 | 236 | _coordinate 237 | (632, 152) 238 | 239 | 240 | _rotation 241 | 0 242 | 243 | 244 | id 245 | mpx_demod_0 246 | 247 | 248 | maxoutbuf 249 | 0 250 | 251 | 252 | minoutbuf 253 | 0 254 | 255 | 256 | samp_rate 257 | samp_rate 258 | 259 | 260 | 261 | pad_sink 262 | 263 | comment 264 | 265 | 266 | 267 | _enabled 268 | True 269 | 270 | 271 | _coordinate 272 | (856, 148) 273 | 274 | 275 | _rotation 276 | 0 277 | 278 | 279 | id 280 | pad_sink_0 281 | 282 | 283 | type 284 | byte 285 | 286 | 287 | label 288 | RDS 289 | 290 | 291 | num_streams 292 | 1 293 | 294 | 295 | optional 296 | False 297 | 298 | 299 | vlen 300 | 1 301 | 302 | 303 | 304 | pad_sink 305 | 306 | comment 307 | 308 | 309 | 310 | _enabled 311 | True 312 | 313 | 314 | _coordinate 315 | (960, 180) 316 | 317 | 318 | _rotation 319 | 0 320 | 321 | 322 | id 323 | pad_sink_0_0 324 | 325 | 326 | type 327 | byte 328 | 329 | 330 | label 331 | stereo 332 | 333 | 334 | num_streams 335 | 1 336 | 337 | 338 | optional 339 | True 340 | 341 | 342 | vlen 343 | 1 344 | 345 | 346 | 347 | pad_sink 348 | 349 | comment 350 | 351 | 352 | 353 | _enabled 354 | True 355 | 356 | 357 | _coordinate 358 | (856, 212) 359 | 360 | 361 | _rotation 362 | 0 363 | 364 | 365 | id 366 | pad_sink_1 367 | 368 | 369 | type 370 | float 371 | 372 | 373 | label 374 | L 375 | 376 | 377 | num_streams 378 | 1 379 | 380 | 381 | optional 382 | False 383 | 384 | 385 | vlen 386 | 1 387 | 388 | 389 | 390 | pad_sink 391 | 392 | comment 393 | 394 | 395 | 396 | _enabled 397 | True 398 | 399 | 400 | _coordinate 401 | (960, 244) 402 | 403 | 404 | _rotation 405 | 0 406 | 407 | 408 | id 409 | pad_sink_2 410 | 411 | 412 | type 413 | float 414 | 415 | 416 | label 417 | R 418 | 419 | 420 | num_streams 421 | 1 422 | 423 | 424 | optional 425 | False 426 | 427 | 428 | vlen 429 | 1 430 | 431 | 432 | 433 | pad_sink 434 | 435 | comment 436 | 437 | 438 | 439 | _enabled 440 | True 441 | 442 | 443 | _coordinate 444 | (848, 276) 445 | 446 | 447 | _rotation 448 | 0 449 | 450 | 451 | id 452 | pad_sink_3 453 | 454 | 455 | type 456 | complex 457 | 458 | 459 | label 460 | const 461 | 462 | 463 | num_streams 464 | 1 465 | 466 | 467 | optional 468 | True 469 | 470 | 471 | vlen 472 | 1 473 | 474 | 475 | 476 | pad_source 477 | 478 | comment 479 | 480 | 481 | 482 | _enabled 483 | True 484 | 485 | 486 | _coordinate 487 | (40, 180) 488 | 489 | 490 | _rotation 491 | 0 492 | 493 | 494 | id 495 | pad_source_0 496 | 497 | 498 | label 499 | in 500 | 501 | 502 | num_streams 503 | 1 504 | 505 | 506 | optional 507 | False 508 | 509 | 510 | type 511 | complex 512 | 513 | 514 | vlen 515 | 1 516 | 517 | 518 | 519 | parameter 520 | 521 | alias 522 | 523 | 524 | 525 | comment 526 | 527 | 528 | 529 | _enabled 530 | True 531 | 532 | 533 | _coordinate 534 | (296, 11) 535 | 536 | 537 | _rotation 538 | 0 539 | 540 | 541 | id 542 | samp_rate 543 | 544 | 545 | label 546 | Sample rate 547 | 548 | 549 | short_id 550 | 551 | 552 | 553 | type 554 | eng_float 555 | 556 | 557 | value 558 | 512e3 559 | 560 | 561 | 562 | analog_quadrature_demod_cf_0 563 | blocks_float_to_complex_0_0 564 | 0 565 | 0 566 | 567 | 568 | blocks_float_to_complex_0_0 569 | mpx_demod_0 570 | 0 571 | 0 572 | 573 | 574 | mpx_demod_0 575 | pad_sink_1 576 | 2 577 | 0 578 | 579 | 580 | mpx_demod_0 581 | pad_sink_2 582 | 3 583 | 0 584 | 585 | 586 | mpx_demod_0 587 | pad_sink_0 588 | 0 589 | 0 590 | 591 | 592 | mpx_demod_0 593 | pad_sink_3 594 | 4 595 | 0 596 | 597 | 598 | mpx_demod_0 599 | pad_sink_0_0 600 | 1 601 | 0 602 | 603 | 604 | pad_source_0 605 | analog_quadrature_demod_cf_0 606 | 0 607 | 0 608 | 609 | 610 | -------------------------------------------------------------------------------- /radio/grc/mpx_demod.grc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fri Aug 25 11:47:02 2017 5 | 6 | options 7 | 8 | author 9 | Alexandre Marquet 10 | 11 | 12 | window_size 13 | 2048, 1024 14 | 15 | 16 | category 17 | RDS 18 | 19 | 20 | comment 21 | 22 | 23 | 24 | description 25 | 26 | 27 | 28 | _enabled 29 | True 30 | 31 | 32 | _coordinate 33 | (8, 8) 34 | 35 | 36 | _rotation 37 | 0 38 | 39 | 40 | generate_options 41 | hb 42 | 43 | 44 | hier_block_src_path 45 | .: 46 | 47 | 48 | id 49 | mpx_demod 50 | 51 | 52 | max_nouts 53 | 0 54 | 55 | 56 | qt_qss_theme 57 | 58 | 59 | 60 | realtime_scheduling 61 | 62 | 63 | 64 | run_command 65 | {python} -u {filename} 66 | 67 | 68 | run_options 69 | prompt 70 | 71 | 72 | run 73 | True 74 | 75 | 76 | thread_safe_setters 77 | 78 | 79 | 80 | title 81 | MPX Demodulator 82 | 83 | 84 | 85 | analog_pll_refout_cc 86 | 87 | alias 88 | 89 | 90 | 91 | comment 92 | 93 | 94 | 95 | affinity 96 | 97 | 98 | 99 | _enabled 100 | 1 101 | 102 | 103 | _coordinate 104 | (376, 460) 105 | 106 | 107 | _rotation 108 | 0 109 | 110 | 111 | id 112 | analog_pll_refout_cc_0 113 | 114 | 115 | w 116 | 2 * math.pi * 8 / samp_rate 117 | 118 | 119 | max_freq 120 | 2 * math.pi * (19000+4) / samp_rate 121 | 122 | 123 | maxoutbuf 124 | 0 125 | 126 | 127 | min_freq 128 | 2 * math.pi * (19000-4) / samp_rate 129 | 130 | 131 | minoutbuf 132 | 0 133 | 134 | 135 | 136 | parameter 137 | 138 | alias 139 | 140 | 141 | 142 | comment 143 | 144 | 145 | 146 | _enabled 147 | True 148 | 149 | 150 | _coordinate 151 | (344, 12) 152 | 153 | 154 | _rotation 155 | 0 156 | 157 | 158 | id 159 | audio_rate 160 | 161 | 162 | label 163 | Audio rate 164 | 165 | 166 | short_id 167 | 168 | 169 | 170 | type 171 | eng_float 172 | 173 | 174 | value 175 | 48000 176 | 177 | 178 | 179 | band_pass_filter 180 | 181 | beta 182 | 6.76 183 | 184 | 185 | alias 186 | 187 | 188 | 189 | comment 190 | 191 | 192 | 193 | affinity 194 | 195 | 196 | 197 | decim 198 | 1 199 | 200 | 201 | _enabled 202 | 1 203 | 204 | 205 | type 206 | fir_filter_ccf 207 | 208 | 209 | _coordinate 210 | (152, 420) 211 | 212 | 213 | _rotation 214 | 0 215 | 216 | 217 | gain 218 | 1 219 | 220 | 221 | high_cutoff_freq 222 | 19e3+500 223 | 224 | 225 | id 226 | band_pass_filter_0_1 227 | 228 | 229 | interp 230 | 1 231 | 232 | 233 | low_cutoff_freq 234 | 19e3-500 235 | 236 | 237 | maxoutbuf 238 | 0 239 | 240 | 241 | minoutbuf 242 | 0 243 | 244 | 245 | samp_rate 246 | samp_rate 247 | 248 | 249 | width 250 | 1e3 251 | 252 | 253 | win 254 | firdes.WIN_HAMMING 255 | 256 | 257 | 258 | blocks_multiply_xx 259 | 260 | alias 261 | 262 | 263 | 264 | comment 265 | 266 | 267 | 268 | affinity 269 | 270 | 271 | 272 | _enabled 273 | 1 274 | 275 | 276 | _coordinate 277 | (728, 352) 278 | 279 | 280 | _rotation 281 | 0 282 | 283 | 284 | id 285 | blocks_multiply_xx_0 286 | 287 | 288 | type 289 | complex 290 | 291 | 292 | maxoutbuf 293 | 0 294 | 295 | 296 | minoutbuf 297 | 0 298 | 299 | 300 | num_inputs 301 | 2 302 | 303 | 304 | vlen 305 | 1 306 | 307 | 308 | 309 | blocks_multiply_xx 310 | 311 | alias 312 | 313 | 314 | 315 | comment 316 | 317 | 318 | 319 | affinity 320 | 321 | 322 | 323 | _enabled 324 | 1 325 | 326 | 327 | _coordinate 328 | (736, 480) 329 | 330 | 331 | _rotation 332 | 0 333 | 334 | 335 | id 336 | blocks_multiply_xx_0_0 337 | 338 | 339 | type 340 | complex 341 | 342 | 343 | maxoutbuf 344 | 0 345 | 346 | 347 | minoutbuf 348 | 0 349 | 350 | 351 | num_inputs 352 | 3 353 | 354 | 355 | vlen 356 | 1 357 | 358 | 359 | 360 | fm_audio_demod 361 | 362 | audio_rate 363 | audio_rate 364 | 365 | 366 | alias 367 | 368 | 369 | 370 | comment 371 | 372 | 373 | 374 | affinity 375 | 376 | 377 | 378 | _enabled 379 | 1 380 | 381 | 382 | _coordinate 383 | (872, 320) 384 | 385 | 386 | _rotation 387 | 0 388 | 389 | 390 | id 391 | fm_audio_demod_0 392 | 393 | 394 | maxoutbuf 395 | 0 396 | 397 | 398 | minoutbuf 399 | 0 400 | 401 | 402 | samp_rate 403 | samp_rate 404 | 405 | 406 | 407 | import 408 | 409 | alias 410 | 411 | 412 | 413 | comment 414 | 415 | 416 | 417 | _enabled 418 | True 419 | 420 | 421 | _coordinate 422 | (464, 12) 423 | 424 | 425 | _rotation 426 | 0 427 | 428 | 429 | id 430 | import_0 431 | 432 | 433 | import 434 | import math 435 | 436 | 437 | 438 | pad_sink 439 | 440 | comment 441 | 442 | 443 | 444 | _enabled 445 | True 446 | 447 | 448 | _coordinate 449 | (1120, 548) 450 | 451 | 452 | _rotation 453 | 0 454 | 455 | 456 | id 457 | pad_sink_0 458 | 459 | 460 | type 461 | byte 462 | 463 | 464 | label 465 | RDS 466 | 467 | 468 | num_streams 469 | 1 470 | 471 | 472 | optional 473 | False 474 | 475 | 476 | vlen 477 | 1 478 | 479 | 480 | 481 | pad_sink 482 | 483 | comment 484 | 485 | 486 | 487 | _enabled 488 | True 489 | 490 | 491 | _coordinate 492 | (1104, 316) 493 | 494 | 495 | _rotation 496 | 0 497 | 498 | 499 | id 500 | pad_sink_0_0 501 | 502 | 503 | type 504 | byte 505 | 506 | 507 | label 508 | stereo 509 | 510 | 511 | num_streams 512 | 1 513 | 514 | 515 | optional 516 | True 517 | 518 | 519 | vlen 520 | 1 521 | 522 | 523 | 524 | pad_sink 525 | 526 | comment 527 | 528 | 529 | 530 | _enabled 531 | True 532 | 533 | 534 | _coordinate 535 | (1200, 348) 536 | 537 | 538 | _rotation 539 | 0 540 | 541 | 542 | id 543 | pad_sink_1 544 | 545 | 546 | type 547 | float 548 | 549 | 550 | label 551 | L 552 | 553 | 554 | num_streams 555 | 1 556 | 557 | 558 | optional 559 | False 560 | 561 | 562 | vlen 563 | 1 564 | 565 | 566 | 567 | pad_sink 568 | 569 | comment 570 | 571 | 572 | 573 | _enabled 574 | True 575 | 576 | 577 | _coordinate 578 | (1104, 380) 579 | 580 | 581 | _rotation 582 | 0 583 | 584 | 585 | id 586 | pad_sink_2 587 | 588 | 589 | type 590 | float 591 | 592 | 593 | label 594 | R 595 | 596 | 597 | num_streams 598 | 1 599 | 600 | 601 | optional 602 | False 603 | 604 | 605 | vlen 606 | 1 607 | 608 | 609 | 610 | pad_sink 611 | 612 | comment 613 | 614 | 615 | 616 | _enabled 617 | True 618 | 619 | 620 | _coordinate 621 | (1120, 604) 622 | 623 | 624 | _rotation 625 | 0 626 | 627 | 628 | id 629 | pad_sink_3 630 | 631 | 632 | type 633 | complex 634 | 635 | 636 | label 637 | const 638 | 639 | 640 | num_streams 641 | 1 642 | 643 | 644 | optional 645 | True 646 | 647 | 648 | vlen 649 | 1 650 | 651 | 652 | 653 | pad_source 654 | 655 | comment 656 | 657 | 658 | 659 | _enabled 660 | True 661 | 662 | 663 | _coordinate 664 | (8, 332) 665 | 666 | 667 | _rotation 668 | 0 669 | 670 | 671 | id 672 | pad_source_0 673 | 674 | 675 | label 676 | in 677 | 678 | 679 | num_streams 680 | 1 681 | 682 | 683 | optional 684 | False 685 | 686 | 687 | type 688 | complex 689 | 690 | 691 | vlen 692 | 1 693 | 694 | 695 | 696 | rds_demod 697 | 698 | alias 699 | 700 | 701 | 702 | comment 703 | 704 | 705 | 706 | affinity 707 | 708 | 709 | 710 | _enabled 711 | 1 712 | 713 | 714 | _coordinate 715 | (904, 552) 716 | 717 | 718 | _rotation 719 | 0 720 | 721 | 722 | id 723 | rds_demod_0 724 | 725 | 726 | maxoutbuf 727 | 0 728 | 729 | 730 | minoutbuf 731 | 0 732 | 733 | 734 | samp_rate 735 | samp_rate 736 | 737 | 738 | 739 | parameter 740 | 741 | alias 742 | 743 | 744 | 745 | comment 746 | 747 | 748 | 749 | _enabled 750 | True 751 | 752 | 753 | _coordinate 754 | (208, 12) 755 | 756 | 757 | _rotation 758 | 0 759 | 760 | 761 | id 762 | samp_rate 763 | 764 | 765 | label 766 | Sample rate 767 | 768 | 769 | short_id 770 | 771 | 772 | 773 | type 774 | eng_float 775 | 776 | 777 | value 778 | 512e3 779 | 780 | 781 | 782 | analog_pll_refout_cc_0 783 | blocks_multiply_xx_0 784 | 0 785 | 0 786 | 787 | 788 | analog_pll_refout_cc_0 789 | blocks_multiply_xx_0 790 | 0 791 | 1 792 | 793 | 794 | analog_pll_refout_cc_0 795 | blocks_multiply_xx_0_0 796 | 0 797 | 0 798 | 799 | 800 | analog_pll_refout_cc_0 801 | blocks_multiply_xx_0_0 802 | 0 803 | 1 804 | 805 | 806 | analog_pll_refout_cc_0 807 | blocks_multiply_xx_0_0 808 | 0 809 | 2 810 | 811 | 812 | band_pass_filter_0_1 813 | analog_pll_refout_cc_0 814 | 0 815 | 0 816 | 817 | 818 | blocks_multiply_xx_0 819 | fm_audio_demod_0 820 | 0 821 | 1 822 | 823 | 824 | blocks_multiply_xx_0_0 825 | rds_demod_0 826 | 0 827 | 0 828 | 829 | 830 | fm_audio_demod_0 831 | pad_sink_1 832 | 1 833 | 0 834 | 835 | 836 | fm_audio_demod_0 837 | pad_sink_2 838 | 2 839 | 0 840 | 841 | 842 | fm_audio_demod_0 843 | pad_sink_0_0 844 | 0 845 | 0 846 | 847 | 848 | pad_source_0 849 | band_pass_filter_0_1 850 | 0 851 | 0 852 | 853 | 854 | pad_source_0 855 | fm_audio_demod_0 856 | 0 857 | 0 858 | 859 | 860 | pad_source_0 861 | rds_demod_0 862 | 0 863 | 1 864 | 865 | 866 | rds_demod_0 867 | pad_sink_0 868 | 0 869 | 0 870 | 871 | 872 | rds_demod_0 873 | pad_sink_3 874 | 1 875 | 0 876 | 877 | 878 | -------------------------------------------------------------------------------- /radio/grc/rds_demod.grc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fri Dec 19 19:33:37 2014 5 | 6 | options 7 | 8 | author 9 | Alexandre Marquet 10 | 11 | 12 | window_size 13 | 1280, 1024 14 | 15 | 16 | category 17 | RDS 18 | 19 | 20 | comment 21 | 22 | 23 | 24 | description 25 | 26 | 27 | 28 | _enabled 29 | True 30 | 31 | 32 | _coordinate 33 | (10, 10) 34 | 35 | 36 | _rotation 37 | 0 38 | 39 | 40 | generate_options 41 | hb 42 | 43 | 44 | hier_block_src_path 45 | .: 46 | 47 | 48 | id 49 | rds_demod 50 | 51 | 52 | max_nouts 53 | 0 54 | 55 | 56 | qt_qss_theme 57 | 58 | 59 | 60 | realtime_scheduling 61 | 62 | 63 | 64 | run_command 65 | {python} -u {filename} 66 | 67 | 68 | run_options 69 | prompt 70 | 71 | 72 | run 73 | True 74 | 75 | 76 | thread_safe_setters 77 | 78 | 79 | 80 | title 81 | RDS demodulator 82 | 83 | 84 | 85 | variable_rrc_filter_taps 86 | 87 | comment 88 | 89 | 90 | 91 | _enabled 92 | 1 93 | 94 | 95 | alpha 96 | 1 97 | 98 | 99 | _coordinate 100 | (544, 20) 101 | 102 | 103 | _rotation 104 | 0 105 | 106 | 107 | gain 108 | 10 109 | 110 | 111 | id 112 | RRC_filtr_taps 113 | 114 | 115 | ntaps 116 | int(6*samp_rate/syms_rate) 117 | 118 | 119 | samp_rate 120 | samp_rate 121 | 122 | 123 | sym_rate 124 | syms_rate 125 | 126 | 127 | 128 | variable 129 | 130 | comment 131 | 132 | 133 | 134 | _enabled 135 | True 136 | 137 | 138 | _coordinate 139 | (320, 68) 140 | 141 | 142 | _rotation 143 | 0 144 | 145 | 146 | id 147 | bitrate 148 | 149 | 150 | value 151 | 1187.5 152 | 153 | 154 | 155 | variable 156 | 157 | comment 158 | 159 | 160 | 161 | _enabled 162 | True 163 | 164 | 165 | _coordinate 166 | (712, 84) 167 | 168 | 169 | _rotation 170 | 0 171 | 172 | 173 | id 174 | decim 175 | 176 | 177 | value 178 | int(samp_rate/(samp_per_sym*syms_rate)) 179 | 180 | 181 | 182 | variable 183 | 184 | comment 185 | 186 | 187 | 188 | _enabled 189 | 1 190 | 191 | 192 | _coordinate 193 | (1056, 20) 194 | 195 | 196 | _rotation 197 | 0 198 | 199 | 200 | id 201 | fsm 202 | 203 | 204 | value 205 | trellis.fsm(prefix+"diff_manchester.fsm") 206 | 207 | 208 | 209 | variable 210 | 211 | comment 212 | 213 | 214 | 215 | _enabled 216 | 1 217 | 218 | 219 | _coordinate 220 | (880, 20) 221 | 222 | 223 | _rotation 224 | 0 225 | 226 | 227 | id 228 | prefix 229 | 230 | 231 | value 232 | "/localdata/marqueal/Documents/sdr/rds/radio/grc/" 233 | 234 | 235 | 236 | variable 237 | 238 | comment 239 | 240 | 241 | 242 | _enabled 243 | True 244 | 245 | 246 | _coordinate 247 | (424, 84) 248 | 249 | 250 | _rotation 251 | 0 252 | 253 | 254 | id 255 | samp_per_sym 256 | 257 | 258 | value 259 | 4 260 | 261 | 262 | 263 | variable 264 | 265 | comment 266 | 267 | 268 | 269 | _enabled 270 | True 271 | 272 | 273 | _coordinate 274 | (440, 20) 275 | 276 | 277 | _rotation 278 | 0 279 | 280 | 281 | id 282 | syms_rate 283 | 284 | 285 | value 286 | 2*bitrate 287 | 288 | 289 | 290 | variable_constellation 291 | 292 | comment 293 | 294 | 295 | 296 | const_points 297 | [-1, 1] 298 | 299 | 300 | type 301 | bpsk 302 | 303 | 304 | dims 305 | 1 306 | 307 | 308 | _enabled 309 | 1 310 | 311 | 312 | _coordinate 313 | (712, 20) 314 | 315 | 316 | _rotation 317 | 0 318 | 319 | 320 | id 321 | variable_constellation_0 322 | 323 | 324 | rot_sym 325 | 2 326 | 327 | 328 | soft_dec_lut 329 | None 330 | 331 | 332 | precision 333 | 8 334 | 335 | 336 | sym_map 337 | [0, 1] 338 | 339 | 340 | 341 | blocks_multiply_xx 342 | 343 | alias 344 | 345 | 346 | 347 | comment 348 | 349 | 350 | 351 | affinity 352 | 353 | 354 | 355 | _enabled 356 | True 357 | 358 | 359 | _coordinate 360 | (176, 216) 361 | 362 | 363 | _rotation 364 | 0 365 | 366 | 367 | id 368 | blocks_multiply_xx_1 369 | 370 | 371 | type 372 | complex 373 | 374 | 375 | maxoutbuf 376 | 0 377 | 378 | 379 | minoutbuf 380 | 0 381 | 382 | 383 | num_inputs 384 | 2 385 | 386 | 387 | vlen 388 | 1 389 | 390 | 391 | 392 | digital_clock_recovery_mm_xx 393 | 394 | alias 395 | 396 | 397 | 398 | comment 399 | 400 | 401 | 402 | affinity 403 | 404 | 405 | 406 | _enabled 407 | 1 408 | 409 | 410 | _coordinate 411 | (256, 548) 412 | 413 | 414 | _rotation 415 | 0 416 | 417 | 418 | gain_mu 419 | 0.175 420 | 421 | 422 | gain_omega 423 | 0.25*0.175*0.175 424 | 425 | 426 | id 427 | digital_clock_recovery_mm_xx_0 428 | 429 | 430 | maxoutbuf 431 | 0 432 | 433 | 434 | minoutbuf 435 | 0 436 | 437 | 438 | mu 439 | 0.5 440 | 441 | 442 | omega_relative_limit 443 | 0.005 444 | 445 | 446 | omega 447 | samp_rate/decim/syms_rate 448 | 449 | 450 | type 451 | complex 452 | 453 | 454 | 455 | digital_lms_dd_equalizer_cc 456 | 457 | alias 458 | 459 | 460 | 461 | comment 462 | 463 | 464 | 465 | cnst 466 | variable_constellation_0 467 | 468 | 469 | affinity 470 | 471 | 472 | 473 | _enabled 474 | 1 475 | 476 | 477 | _coordinate 478 | (464, 556) 479 | 480 | 481 | _rotation 482 | 0 483 | 484 | 485 | mu 486 | 0.1 487 | 488 | 489 | id 490 | digital_lms_dd_equalizer_cc_0_0 491 | 492 | 493 | maxoutbuf 494 | 0 495 | 496 | 497 | minoutbuf 498 | 0 499 | 500 | 501 | num_taps 502 | 1 503 | 504 | 505 | sps 506 | 1 507 | 508 | 509 | 510 | fir_filter_xxx 511 | 512 | alias 513 | 514 | 515 | 516 | comment 517 | 518 | 519 | 520 | affinity 521 | 522 | 523 | 524 | decim 525 | decim 526 | 527 | 528 | _enabled 529 | 1 530 | 531 | 532 | _coordinate 533 | (56, 572) 534 | 535 | 536 | _rotation 537 | 0 538 | 539 | 540 | id 541 | fir_filter_xxx_0 542 | 543 | 544 | maxoutbuf 545 | 0 546 | 547 | 548 | minoutbuf 549 | 0 550 | 551 | 552 | samp_delay 553 | 0 554 | 555 | 556 | taps 557 | RRC_filtr_taps 558 | 559 | 560 | type 561 | ccf 562 | 563 | 564 | 565 | import 566 | 567 | alias 568 | 569 | 570 | 571 | comment 572 | 573 | 574 | 575 | _enabled 576 | True 577 | 578 | 579 | _coordinate 580 | (320, 20) 581 | 582 | 583 | _rotation 584 | 0 585 | 586 | 587 | id 588 | import_1 589 | 590 | 591 | import 592 | import numpy 593 | 594 | 595 | 596 | pad_sink 597 | 598 | comment 599 | 600 | 601 | 602 | _enabled 603 | True 604 | 605 | 606 | _coordinate 607 | (1152, 484) 608 | 609 | 610 | _rotation 611 | 0 612 | 613 | 614 | id 615 | pad_sink_0 616 | 617 | 618 | type 619 | byte 620 | 621 | 622 | label 623 | RDS 624 | 625 | 626 | num_streams 627 | 1 628 | 629 | 630 | optional 631 | False 632 | 633 | 634 | vlen 635 | 1 636 | 637 | 638 | 639 | pad_sink 640 | 641 | comment 642 | 643 | 644 | 645 | _enabled 646 | 1 647 | 648 | 649 | _coordinate 650 | (1152, 180) 651 | 652 | 653 | _rotation 654 | 0 655 | 656 | 657 | id 658 | pad_sink_1 659 | 660 | 661 | type 662 | complex 663 | 664 | 665 | label 666 | const 667 | 668 | 669 | num_streams 670 | 1 671 | 672 | 673 | optional 674 | True 675 | 676 | 677 | vlen 678 | 1 679 | 680 | 681 | 682 | pad_source 683 | 684 | comment 685 | 686 | 687 | 688 | _enabled 689 | True 690 | 691 | 692 | _coordinate 693 | (8, 283) 694 | 695 | 696 | _rotation 697 | 0 698 | 699 | 700 | id 701 | pad_source_0 702 | 703 | 704 | label 705 | carrier 706 | 707 | 708 | num_streams 709 | 1 710 | 711 | 712 | optional 713 | False 714 | 715 | 716 | type 717 | complex 718 | 719 | 720 | vlen 721 | 1 722 | 723 | 724 | 725 | pad_source 726 | 727 | comment 728 | 729 | 730 | 731 | _enabled 732 | True 733 | 734 | 735 | _coordinate 736 | (8, 171) 737 | 738 | 739 | _rotation 740 | 0 741 | 742 | 743 | id 744 | pad_source_0_0 745 | 746 | 747 | label 748 | MPX 749 | 750 | 751 | num_streams 752 | 1 753 | 754 | 755 | optional 756 | False 757 | 758 | 759 | type 760 | complex 761 | 762 | 763 | vlen 764 | 1 765 | 766 | 767 | 768 | parameter 769 | 770 | alias 771 | 772 | 773 | 774 | comment 775 | 776 | 777 | 778 | _enabled 779 | True 780 | 781 | 782 | _coordinate 783 | (200, 20) 784 | 785 | 786 | _rotation 787 | 0 788 | 789 | 790 | id 791 | samp_rate 792 | 793 | 794 | label 795 | Sample rate 796 | 797 | 798 | short_id 799 | 800 | 801 | 802 | type 803 | eng_float 804 | 805 | 806 | value 807 | 512e3 808 | 809 | 810 | 811 | trellis_metrics_x 812 | 813 | alias 814 | 815 | 816 | 817 | comment 818 | 819 | 820 | 821 | table 822 | -1, -1, -1, 1, 1, -1, 1, 1 823 | 824 | 825 | affinity 826 | 827 | 828 | 829 | dim 830 | 2 831 | 832 | 833 | _enabled 834 | 1 835 | 836 | 837 | _coordinate 838 | (688, 556) 839 | 840 | 841 | _rotation 842 | 0 843 | 844 | 845 | id 846 | trellis_metrics_x_0 847 | 848 | 849 | maxoutbuf 850 | 0 851 | 852 | 853 | metric_type 854 | digital.TRELLIS_EUCLIDEAN 855 | 856 | 857 | minoutbuf 858 | 0 859 | 860 | 861 | card 862 | fsm.O() 863 | 864 | 865 | type 866 | c 867 | 868 | 869 | 870 | trellis_viterbi_x 871 | 872 | alias 873 | 874 | 875 | 876 | block_size 877 | 10000 878 | 879 | 880 | comment 881 | 882 | 883 | 884 | affinity 885 | 886 | 887 | 888 | _enabled 889 | 1 890 | 891 | 892 | fsm_args 893 | fsm 894 | 895 | 896 | final_state 897 | -1 898 | 899 | 900 | _coordinate 901 | (904, 556) 902 | 903 | 904 | _rotation 905 | 0 906 | 907 | 908 | id 909 | trellis_viterbi_x_0 910 | 911 | 912 | init_state 913 | 0 914 | 915 | 916 | maxoutbuf 917 | 0 918 | 919 | 920 | minoutbuf 921 | 0 922 | 923 | 924 | type 925 | b 926 | 927 | 928 | 929 | blocks_multiply_xx_1 930 | fir_filter_xxx_0 931 | 0 932 | 0 933 | 934 | 935 | digital_clock_recovery_mm_xx_0 936 | digital_lms_dd_equalizer_cc_0_0 937 | 0 938 | 0 939 | 940 | 941 | digital_lms_dd_equalizer_cc_0_0 942 | pad_sink_1 943 | 0 944 | 0 945 | 946 | 947 | digital_lms_dd_equalizer_cc_0_0 948 | trellis_metrics_x_0 949 | 0 950 | 0 951 | 952 | 953 | fir_filter_xxx_0 954 | digital_clock_recovery_mm_xx_0 955 | 0 956 | 0 957 | 958 | 959 | pad_source_0 960 | blocks_multiply_xx_1 961 | 0 962 | 1 963 | 964 | 965 | pad_source_0_0 966 | blocks_multiply_xx_1 967 | 0 968 | 0 969 | 970 | 971 | trellis_metrics_x_0 972 | trellis_viterbi_x_0 973 | 0 974 | 0 975 | 976 | 977 | trellis_viterbi_x_0 978 | pad_sink_0 979 | 0 980 | 0 981 | 982 | 983 | -------------------------------------------------------------------------------- /radio/mpx_demod.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################## 3 | # GNU Radio Python Flow Graph 4 | # Title: MPX Demodulator 5 | # Author: Alexandre Marquet 6 | # Generated: Fri Aug 25 15:36:33 2017 7 | ################################################## 8 | 9 | import os 10 | import sys 11 | sys.path.append(os.environ.get('GRC_HIER_PATH', os.path.expanduser('~/.grc_gnuradio'))) 12 | 13 | from fm_audio_demod import fm_audio_demod # grc-generated hier_block 14 | from gnuradio import analog 15 | from gnuradio import blocks 16 | from gnuradio import filter 17 | from gnuradio import gr 18 | from gnuradio.filter import firdes 19 | from rds_demod import rds_demod # grc-generated hier_block 20 | import math 21 | 22 | 23 | class mpx_demod(gr.hier_block2): 24 | 25 | def __init__(self, audio_rate=48000, samp_rate=512e3): 26 | gr.hier_block2.__init__( 27 | self, "MPX Demodulator", 28 | gr.io_signature(1, 1, gr.sizeof_gr_complex*1), 29 | gr.io_signaturev(5, 5, [gr.sizeof_char*1, gr.sizeof_char*1, gr.sizeof_float*1, gr.sizeof_float*1, gr.sizeof_gr_complex*1]), 30 | ) 31 | 32 | ################################################## 33 | # Parameters 34 | ################################################## 35 | self.audio_rate = audio_rate 36 | self.samp_rate = samp_rate 37 | 38 | ################################################## 39 | # Blocks 40 | ################################################## 41 | self.rds_demod_0 = rds_demod( 42 | samp_rate=samp_rate, 43 | ) 44 | self.fm_audio_demod_0 = fm_audio_demod( 45 | audio_rate=audio_rate, 46 | samp_rate=samp_rate, 47 | ) 48 | self.blocks_multiply_xx_0_0 = blocks.multiply_vcc(1) 49 | self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) 50 | self.band_pass_filter_0_1 = filter.fir_filter_ccf(1, firdes.band_pass( 51 | 1, samp_rate, 19e3-500, 19e3+500, 1e3, firdes.WIN_HAMMING, 6.76)) 52 | self.analog_pll_refout_cc_0 = analog.pll_refout_cc(2 * math.pi * 8 / samp_rate, 2 * math.pi * (19000+4) / samp_rate, 2 * math.pi * (19000-4) / samp_rate) 53 | 54 | ################################################## 55 | # Connections 56 | ################################################## 57 | self.connect((self.analog_pll_refout_cc_0, 0), (self.blocks_multiply_xx_0, 0)) 58 | self.connect((self.analog_pll_refout_cc_0, 0), (self.blocks_multiply_xx_0, 1)) 59 | self.connect((self.analog_pll_refout_cc_0, 0), (self.blocks_multiply_xx_0_0, 0)) 60 | self.connect((self.analog_pll_refout_cc_0, 0), (self.blocks_multiply_xx_0_0, 1)) 61 | self.connect((self.analog_pll_refout_cc_0, 0), (self.blocks_multiply_xx_0_0, 2)) 62 | self.connect((self.band_pass_filter_0_1, 0), (self.analog_pll_refout_cc_0, 0)) 63 | self.connect((self.blocks_multiply_xx_0, 0), (self.fm_audio_demod_0, 1)) 64 | self.connect((self.blocks_multiply_xx_0_0, 0), (self.rds_demod_0, 0)) 65 | self.connect((self.fm_audio_demod_0, 0), (self, 1)) 66 | self.connect((self.fm_audio_demod_0, 1), (self, 2)) 67 | self.connect((self.fm_audio_demod_0, 2), (self, 3)) 68 | self.connect((self, 0), (self.band_pass_filter_0_1, 0)) 69 | self.connect((self, 0), (self.fm_audio_demod_0, 0)) 70 | self.connect((self, 0), (self.rds_demod_0, 1)) 71 | self.connect((self.rds_demod_0, 0), (self, 0)) 72 | self.connect((self.rds_demod_0, 1), (self, 4)) 73 | 74 | def get_audio_rate(self): 75 | return self.audio_rate 76 | 77 | def set_audio_rate(self, audio_rate): 78 | self.audio_rate = audio_rate 79 | self.fm_audio_demod_0.set_audio_rate(self.audio_rate) 80 | 81 | def get_samp_rate(self): 82 | return self.samp_rate 83 | 84 | def set_samp_rate(self, samp_rate): 85 | self.samp_rate = samp_rate 86 | self.rds_demod_0.set_samp_rate(self.samp_rate) 87 | self.fm_audio_demod_0.set_samp_rate(self.samp_rate) 88 | self.band_pass_filter_0_1.set_taps(firdes.band_pass(1, self.samp_rate, 19e3-500, 19e3+500, 1e3, firdes.WIN_HAMMING, 6.76)) 89 | self.analog_pll_refout_cc_0.set_loop_bandwidth(2 * math.pi * 8 / self.samp_rate) 90 | self.analog_pll_refout_cc_0.set_max_freq(2 * math.pi * (19000+4) / self.samp_rate) 91 | self.analog_pll_refout_cc_0.set_min_freq(2 * math.pi * (19000-4) / self.samp_rate) 92 | -------------------------------------------------------------------------------- /radio/rds_demod.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################## 3 | # GNU Radio Python Flow Graph 4 | # Title: RDS demodulator 5 | # Author: Alexandre Marquet 6 | # Generated: Fri Aug 25 12:27:37 2017 7 | ################################################## 8 | 9 | from gnuradio import blocks 10 | from gnuradio import digital 11 | from gnuradio import filter 12 | from gnuradio import gr 13 | from gnuradio import trellis 14 | from gnuradio import trellis, digital 15 | from gnuradio.filter import firdes 16 | import numpy 17 | 18 | 19 | class rds_demod(gr.hier_block2): 20 | 21 | def __init__(self, samp_rate=512e3): 22 | gr.hier_block2.__init__( 23 | self, "RDS demodulator", 24 | gr.io_signaturev(2, 2, [gr.sizeof_gr_complex*1, gr.sizeof_gr_complex*1]), 25 | gr.io_signaturev(2, 2, [gr.sizeof_char*1, gr.sizeof_gr_complex*1]), 26 | ) 27 | 28 | ################################################## 29 | # Parameters 30 | ################################################## 31 | self.samp_rate = samp_rate 32 | 33 | ################################################## 34 | # Variables 35 | ################################################## 36 | self.bitrate = bitrate = 1187.5 37 | self.syms_rate = syms_rate = 2*bitrate 38 | self.samp_per_sym = samp_per_sym = 4 39 | self.prefix = prefix = "radio/" 40 | 41 | self.variable_constellation_0 = variable_constellation_0 = digital.constellation_bpsk().base() 42 | 43 | self.fsm = fsm = trellis.fsm(prefix+"diff_manchester.fsm") 44 | self.decim = decim = int(samp_rate/(samp_per_sym*syms_rate)) 45 | 46 | self.RRC_filtr_taps = RRC_filtr_taps = firdes.root_raised_cosine(10, samp_rate, syms_rate, 1, int(6*samp_rate/syms_rate)) 47 | 48 | 49 | ################################################## 50 | # Blocks 51 | ################################################## 52 | self.trellis_viterbi_x_0 = trellis.viterbi_b(trellis.fsm(fsm), 10000, 0, -1) 53 | self.trellis_metrics_x_0 = trellis.metrics_c(fsm.O(), 2, (-1, -1, -1, 1, 1, -1, 1, 1), digital.TRELLIS_EUCLIDEAN) 54 | self.fir_filter_xxx_0 = filter.fir_filter_ccf(decim, (RRC_filtr_taps)) 55 | self.fir_filter_xxx_0.declare_sample_delay(0) 56 | self.digital_lms_dd_equalizer_cc_0_0 = digital.lms_dd_equalizer_cc(1, 0.1, 1, variable_constellation_0) 57 | self.digital_clock_recovery_mm_xx_0 = digital.clock_recovery_mm_cc(samp_rate/decim/syms_rate, 0.25*0.175*0.175, 0.5, 0.175, 0.005) 58 | self.blocks_multiply_xx_1 = blocks.multiply_vcc(1) 59 | 60 | ################################################## 61 | # Connections 62 | ################################################## 63 | self.connect((self.blocks_multiply_xx_1, 0), (self.fir_filter_xxx_0, 0)) 64 | self.connect((self.digital_clock_recovery_mm_xx_0, 0), (self.digital_lms_dd_equalizer_cc_0_0, 0)) 65 | self.connect((self.digital_lms_dd_equalizer_cc_0_0, 0), (self, 1)) 66 | self.connect((self.digital_lms_dd_equalizer_cc_0_0, 0), (self.trellis_metrics_x_0, 0)) 67 | self.connect((self.fir_filter_xxx_0, 0), (self.digital_clock_recovery_mm_xx_0, 0)) 68 | self.connect((self, 0), (self.blocks_multiply_xx_1, 1)) 69 | self.connect((self, 1), (self.blocks_multiply_xx_1, 0)) 70 | self.connect((self.trellis_metrics_x_0, 0), (self.trellis_viterbi_x_0, 0)) 71 | self.connect((self.trellis_viterbi_x_0, 0), (self, 0)) 72 | 73 | def get_samp_rate(self): 74 | return self.samp_rate 75 | 76 | def set_samp_rate(self, samp_rate): 77 | self.samp_rate = samp_rate 78 | self.set_decim(int(self.samp_rate/(self.samp_per_sym*self.syms_rate))) 79 | self.digital_clock_recovery_mm_xx_0.set_omega(self.samp_rate/self.decim/self.syms_rate) 80 | 81 | def get_bitrate(self): 82 | return self.bitrate 83 | 84 | def set_bitrate(self, bitrate): 85 | self.bitrate = bitrate 86 | self.set_syms_rate(2*self.bitrate) 87 | 88 | def get_syms_rate(self): 89 | return self.syms_rate 90 | 91 | def set_syms_rate(self, syms_rate): 92 | self.syms_rate = syms_rate 93 | self.set_decim(int(self.samp_rate/(self.samp_per_sym*self.syms_rate))) 94 | self.digital_clock_recovery_mm_xx_0.set_omega(self.samp_rate/self.decim/self.syms_rate) 95 | 96 | def get_samp_per_sym(self): 97 | return self.samp_per_sym 98 | 99 | def set_samp_per_sym(self, samp_per_sym): 100 | self.samp_per_sym = samp_per_sym 101 | self.set_decim(int(self.samp_rate/(self.samp_per_sym*self.syms_rate))) 102 | 103 | def get_prefix(self): 104 | return self.prefix 105 | 106 | def set_prefix(self, prefix): 107 | self.prefix = prefix 108 | self.set_fsm(trellis.fsm(self.prefix+"diff_manchester.fsm")) 109 | 110 | def get_variable_constellation_0(self): 111 | return self.variable_constellation_0 112 | 113 | def set_variable_constellation_0(self, variable_constellation_0): 114 | self.variable_constellation_0 = variable_constellation_0 115 | 116 | def get_fsm(self): 117 | return self.fsm 118 | 119 | def set_fsm(self, fsm): 120 | self.fsm = fsm 121 | self.trellis_viterbi_x_0.set_FSM(trellis.fsm(self.fsm)) 122 | 123 | def get_decim(self): 124 | return self.decim 125 | 126 | def set_decim(self, decim): 127 | self.decim = decim 128 | self.digital_clock_recovery_mm_xx_0.set_omega(self.samp_rate/self.decim/self.syms_rate) 129 | 130 | def get_RRC_filtr_taps(self): 131 | return self.RRC_filtr_taps 132 | 133 | def set_RRC_filtr_taps(self, RRC_filtr_taps): 134 | self.RRC_filtr_taps = RRC_filtr_taps 135 | self.fir_filter_xxx_0.set_taps((self.RRC_filtr_taps)) 136 | -------------------------------------------------------------------------------- /radio/rds_synchronizer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pmt 4 | import numpy 5 | from decoder import event_handler as event 6 | from gnuradio import gr 7 | 8 | class rds_synchronizer(gr.sync_block): 9 | #Class attributes 10 | #Parity-check matrix 11 | H=[ 12 | [1, 0, 1, 1, 0, 1, 1, 1, 0, 0], 13 | [0, 1, 0, 1, 1, 0, 1, 1, 1, 0], 14 | [0, 0, 1, 0, 1, 1, 0, 1, 1, 1], 15 | [1, 0, 1, 0, 0, 0, 0, 1, 1, 1], 16 | [1, 1, 1, 0, 0, 1, 1, 1, 1, 1], 17 | [1, 1, 0, 0, 0, 1, 0, 0, 1, 1], 18 | [1, 1, 0, 1, 0, 1, 0, 1, 0, 1], 19 | [1, 1, 0, 1, 1, 1, 0, 1, 1, 0], 20 | [0, 1, 1, 0, 1, 1, 1, 0, 1, 1], 21 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 1], 22 | [1, 1, 1, 1, 0, 1, 1, 1, 0, 0], 23 | [0, 1, 1, 1, 1, 0, 1, 1, 1, 0], 24 | [0, 0, 1, 1, 1, 1, 0, 1, 1, 1], 25 | [1, 0, 1, 0, 1, 0, 0, 1, 1, 1], 26 | [1, 1, 1, 0, 0, 0, 1, 1, 1, 1], 27 | [1, 1, 0, 0, 0, 1, 1, 0, 1, 1]]; 28 | H=numpy.concatenate((numpy.identity(10, int), H), axis=0); 29 | H=numpy.matrix(H); 30 | # see Annex B, table B.1 in the standard 31 | # offset word C' has been put at the end 32 | offset_pos=[0,1,2,3,2]; 33 | offset_name=["A","B","C","D","C'"]; 34 | offset_word=[ 35 | 0b0011111100, 36 | 0b0110011000, 37 | 0b0101101000, 38 | 0b0110110100, 39 | 0b1101010000]; 40 | syndrome=[ 41 | 0b1111011000, 42 | 0b1111010100, 43 | 0b1001011100, 44 | 0b1001011000, 45 | 0b1111001100]; 46 | syndrome_err_patterns=dict(); 47 | for i in range(0,22): 48 | for j in range(0,2**5): 49 | e=numpy.zeros(26, numpy.int) 50 | e[i]=j&0b01; 51 | e[i+1]=(j>>1)&0b01; 52 | e[i+2]=(j>>2)&0b01; 53 | e[i+3]=(j>>3)&0b01; 54 | e[i+4]=(j>>4)&0b01; 55 | e=numpy.matrix(e); 56 | 57 | s=numpy.mod(e*H,2); 58 | 59 | #We convert e and s to integers 60 | e_int=0; 61 | for k in range(0,26): 62 | e_int=(e_int<<1)|e[0,k]; 63 | 64 | s_int=0; 65 | for k in range(0,10): 66 | s_int=(s_int<<1)|s[0,k]; 67 | 68 | syndrome_err_patterns[s_int]=e_int; 69 | 70 | def __init__(self): 71 | #Parent constructor 72 | gr.sync_block.__init__(self, 73 | name="RDS sink", 74 | in_sig=[ numpy.uint8 ], 75 | out_sig=None); 76 | 77 | #Register message ports 78 | self.message_port_register_out(pmt.intern('Group')); 79 | 80 | #Attributes definitions 81 | self.reset_attr(); 82 | 83 | #Events definitions 84 | self.sync_event=event.event(); #fire(True) if synced, fire(False) else 85 | 86 | def reset_attr(self): 87 | self.sync_bit_counter = 0; 88 | self.lastseen_offset_counter = 0; 89 | self.block_bit_counter = 0; 90 | self.wrong_blocks_counter = 0; 91 | self.blocks_counter = 0; 92 | self.group = [0, 0, 0, 0]; 93 | self.presync = False; 94 | self.group_assembly_started = False; 95 | self.lastseen_offset = 0; 96 | self.block_number = 0; 97 | self.reg = 0; 98 | self.synced = False; 99 | 100 | def enter_no_sync(self): 101 | self.reset_attr(); 102 | #Fire event 103 | self.sync_event.fire(False); 104 | 105 | def enter_sync(self, sync_block_number): 106 | self.wrong_blocks_counter = 0; 107 | self.blocks_counter = 0; 108 | self.block_bit_counter = 0; 109 | self.block_number = (sync_block_number + 1) % 4; 110 | self.group_assembly_started = 0; 111 | self.synced = True; 112 | #Fire event 113 | self.sync_event.fire(True); 114 | 115 | def aquire_sync(self): 116 | i=0; 117 | reg_syndrome=0; 118 | bit_distance=0; 119 | block_distance=0; 120 | 121 | reg_syndrome = self.calc_syndrome(self.reg); 122 | for i in range(0,5): 123 | if (reg_syndrome == self.syndrome[i]): 124 | if (self.presync == False): 125 | self.lastseen_offset = i; 126 | self.lastseen_offset_counter=self.sync_bit_counter; 127 | self.presync=True; 128 | else: 129 | bit_distance = self.sync_bit_counter - self.lastseen_offset_counter; 130 | self.presync = False; 131 | self.sync_bit_counter=0; 132 | if (bit_distance%26 == 0): 133 | block_distance=bit_distance/26; 134 | if((block_distance < 3) and (self.offset_pos[self.lastseen_offset] < self.offset_pos[i])): 135 | self.enter_sync(i); 136 | break; #syndrome found, no more cycles 137 | 138 | def calc_syndrome(self, seq): 139 | vec_seq=numpy.fromstring(numpy.binary_repr(seq,26), dtype=numpy.uint8)-48; 140 | vec_seq=numpy.matrix(vec_seq); 141 | 142 | syndrome=numpy.mod(vec_seq*self.H, 2); 143 | syndrome_int=numpy.packbits(syndrome); 144 | syndrome_int=(syndrome_int[0,0]<<2) + (syndrome_int[0,1]>>6); 145 | 146 | return syndrome_int 147 | 148 | def correct_errors(self, seq, block_number): 149 | #Remove syndrome corresponding to the offset word 150 | seq=seq ^ self.offset_word[block_number]; 151 | 152 | #Compute syndrome 153 | syndrome=self.calc_syndrome(seq); 154 | 155 | #Decode 156 | try: 157 | m_dec=(seq^self.syndrome_err_patterns[syndrome])>>10; 158 | except KeyError as e: 159 | return -1; 160 | 161 | return m_dec; 162 | 163 | def work(self, input_items, output_items): 164 | in0=input_items[0]; 165 | 166 | for item in in0: 167 | # the synchronization process is described in Annex C 168 | # page 66 of the standard 169 | dataword=0; 170 | #reg contains the last 26 rds bits (last block) 171 | self.reg=(self.reg<<1)|item; 172 | #Limit the size of the register to 26 bits 173 | self.reg=self.reg&0x3FFFFFF; 174 | 175 | if(self.synced == False): 176 | self.sync_bit_counter += 1; 177 | self.aquire_sync(); 178 | else: 179 | self.block_bit_counter = (self.block_bit_counter + 1)%26; 180 | # wait until 26 bits enter the buffer 181 | if(self.block_bit_counter == 0): 182 | #Error correction and detection 183 | corrected_seq=self.correct_errors(self.reg, self.block_number); 184 | if(corrected_seq != -1): 185 | dataword=corrected_seq; 186 | # manage special case of C or C' offset word 187 | elif ((self.block_number == 2)): 188 | corrected_seq=self.correct_errors(self.reg, 4); 189 | if(corrected_seq != -1): 190 | dataword=corrected_seq; 191 | else: 192 | self.wrong_blocks_counter += 1; 193 | else: 194 | self.wrong_blocks_counter += 1; 195 | 196 | # done checking CRC 197 | if (self.block_number == 0 and corrected_seq != -1): 198 | self.group_assembly_started = True; 199 | 200 | if (self.group_assembly_started): 201 | if (corrected_seq == -1): 202 | self.group_assembly_started = False; 203 | else: 204 | self.group[self.block_number] = dataword; 205 | 206 | if (self.block_number == 3): 207 | self.message_port_pub( 208 | pmt.intern('Group'), 209 | pmt.to_pmt(numpy.array(self.group, dtype=numpy.uint16))); 210 | 211 | self.block_number = (self.block_number + 1) % 4; 212 | self.blocks_counter += 1; 213 | 214 | #1187.5 bps / 104 bits = 11.4 groups/sec, or 45.7 blocks/sec */ 215 | if (self.blocks_counter==50): 216 | if (self.wrong_blocks_counter>25): 217 | #Lost sync 218 | self.enter_no_sync(); 219 | 220 | self.blocks_counter=0; 221 | self.wrong_blocks_counter=0; 222 | 223 | return len(input_items[0]); 224 | -------------------------------------------------------------------------------- /rds_handlers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import datetime 3 | 4 | class rds_handlers(): 5 | #Synchronizer event handlers 6 | def log_sync(self,sync_state): 7 | if sync_state: 8 | print "***SYNCED***" 9 | else: 10 | print "***LOST SYNC***" 11 | 12 | #Decoder event handlers 13 | def print_ps_name(self,rds_decoder): 14 | print "Station: ", 15 | print ''.join(rds_decoder.ps_name); 16 | 17 | def print_radiotext(self,rds_decoder): 18 | print "Radiotext: ", 19 | print ''.join(rds_decoder.rt); 20 | 21 | def print_af(self,rds_decoder): 22 | print "Alternative frequencies:" 23 | af_list=list(rds_decoder.af_list); 24 | af_list.sort(); 25 | for af in af_list: 26 | print '-',; 27 | if(af > 1e3): 28 | print af/1e3,; 29 | print 'MHz'; 30 | else: 31 | print af,; 32 | print 'kHz'; 33 | print "\n"; 34 | 35 | def print_date(self,rds_decoder): 36 | print "Date: ", 37 | print rds_decoder.clocktime.strftime("%d/%m/%Y %H:%M %z"); 38 | 39 | def print_eon_info(self,rds_decoder): 40 | print "Information on network ", 41 | print rds_decoder.pi, 42 | print " received\n" 43 | print "(EON) Station: ", 44 | print rds_decoder.ondict[rds_decoder.pi].ps_name; 45 | print "(EON) Alternative frequencies (kHz): ", 46 | for af in rds_decoder.ondict[rds_decoder.pi].af_list: 47 | print af,; 48 | print ", "; 49 | print "\n"; 50 | --------------------------------------------------------------------------------