diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt index cfde701..bb6cc3a 100644 --- a/grc/CMakeLists.txt +++ b/grc/CMakeLists.txt @@ -31,5 +31,6 @@ install(FILES crfa_sync_decim.xml crfa_rds_decoder_redsea.xml crfa_qtgui_range.xml - crfa_variable_setter.xml DESTINATION share/gnuradio/grc/blocks + crfa_variable_setter.xml + crfa_tmc_parser.xml DESTINATION share/gnuradio/grc/blocks ) diff --git a/grc/crfa_rds_parser_table_qt.xml b/grc/crfa_rds_parser_table_qt.xml index 8f0d1c7..a7d3f5c 100644 --- a/grc/crfa_rds_parser_table_qt.xml +++ b/grc/crfa_rds_parser_table_qt.xml @@ -5,7 +5,9 @@ [crfa] import crfa from crfa.rds_parser_table_qt import rds_parser_table_qt, rds_parser_table_qt_Widget,rds_parser_table_qt_Signals + #set $win = 'self._%s_win'%$id #set $signals = 'self._%s_signals'%$id #if not $label() @@ -108,7 +110,7 @@ $(gui_hint()($win)) - + + work directory + workdir + + string + part + + + Label + label + + string + #if $label() then 'none' else 'part'# + + + Log + log + False + bool + + + + + Debug + debug + False + bool + + + + + write Database + writeDB + False + bool + + + + + + GUI Hint + gui_hint + + gui_hint + part + + + open($workdir+"event-list_with_forecast_sort.csv").close() or True + + + open($workdir+"directory_writable","w").close() or True + + + in + message + 1 + + diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index aacd8b1..efb279a 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -43,7 +43,8 @@ GR_PYTHON_INSTALL( decoder_compare.py qtgui_range.py variable_setter.py - tmc_classes.py DESTINATION ${GR_PYTHON_DIR}/crfa + tmc_classes.py + tmc_parser.py DESTINATION ${GR_PYTHON_DIR}/crfa ) ######################################################################## diff --git a/python/__init__.py b/python/__init__.py index a08616d..7517306 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -43,4 +43,5 @@ from vector_cutter import vector_cutter from decoder_compare import decoder_compare from qtgui_range import qtgui_range from variable_setter import variable_setter +from tmc_parser import tmc_parser # diff --git a/python/rds_parser_table_qt.py b/python/rds_parser_table_qt.py index 45e3004..d1a1409 100644 --- a/python/rds_parser_table_qt.py +++ b/python/rds_parser_table_qt.py @@ -1,23 +1,23 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# +# # Copyright 2016 <+YOU OR YOUR COMPANY+>. -# +# # This is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. -# +# # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this software; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. -# +# from __future__ import print_function#print without newline print('.', end="") import numpy from gnuradio import gr @@ -37,7 +37,7 @@ pr = cProfile.Profile() #from threading import Timer#to periodically save DB from PyQt4.QtCore import QObject, pyqtSignal -from bitstring import BitArray +from bitstring import BitArray language="de"#currently supported: de, en (both partially) @@ -45,42 +45,43 @@ language="de"#currently supported: de, en (both partially) class rds_parser_table_qt_Signals(QObject): DataUpdateEvent = QtCore.pyqtSignal(dict) def __init__(self, parent=None): - super(QtCore.QObject, self).__init__() + super(QtCore.QObject, self).__init__() class rds_parser_table_qt(gr.sync_block):#START """ docstring for block qtguitest """ def goodbye(self): - self.clean_data_and_commit_db() - print("quitting rds parser table, closing db") - if self.writeDB: - #self.db.commit() - self.db.close() + self.clean_data_and_commit_db() + print("quitting rds parser table, closing db") + if self.writeDB: + #self.db.commit() + self.db.close() def __init__(self,signals,nPorts,slot,freq,log,debug,workdir,writeDB,showTMC): - #QObject.__init__() gr.sync_block.__init__(self, name="RDS Table", in_sig=None, out_sig=None) - if nPorts==1: - self.message_port_register_in(pmt.intern('in')) - self.set_msg_handler(pmt.intern('in'), functools.partial(self.handle_msg, port=0)) - else: - for i in range(nPorts): - self.message_port_register_in(pmt.intern('in%d'%i)) - self.set_msg_handler(pmt.intern('in%d'%i), functools.partial(self.handle_msg, port=i)) - self.nPorts=nPorts - self.message_port_register_in(pmt.intern('freq')) - self.set_msg_handler(pmt.intern('freq'), self.set_freq) - self.message_port_register_out(pmt.intern('ctrl')) - self.log=log - self.debug=debug - self.writeDB=writeDB - self.showTMC=showTMC - self.signals=signals - self.RDS_data={} - self.change_freq_tune=slot - self.tuning_frequency=int(freq) + if nPorts==1: + self.message_port_register_in(pmt.intern('in')) + self.set_msg_handler(pmt.intern('in'), functools.partial(self.handle_msg, port=0)) + else: + for i in range(nPorts): + self.message_port_register_in(pmt.intern('in%d'%i)) + self.set_msg_handler(pmt.intern('in%d'%i), functools.partial(self.handle_msg, port=i)) + self.nPorts=nPorts + self.message_port_register_in(pmt.intern('freq')) + self.set_msg_handler(pmt.intern('freq'), self.set_freq) + self.message_port_register_out(pmt.intern('ctrl')) + self.message_port_register_out(pmt.intern('tmc_raw')) + + self.log=log + self.debug=debug + self.writeDB=writeDB + self.showTMC=showTMC + self.signals=signals + self.RDS_data={} + self.change_freq_tune=slot + self.tuning_frequency=int(freq) self.printcounter=0 self.ODA_application_names={} self.TMC_data={} @@ -88,958 +89,964 @@ class rds_parser_table_qt(gr.sync_block):#START self.decoder_frequencies={} self.decoders=[] for i in range(nPorts): - self.decoders.append({'synced':False,'freq':None}) + self.decoders.append({'synced':False,'freq':None}) #self.decoder_synced={} #self.colorder=['ID','freq','name','PTY','AF','time','text','quality','buttons'] self.colorder=['ID','freq','name','buttons','PTY','AF','time','text','quality','RT+'] self.workdir=workdir self.PI_dict={}#contains PI:numpackets (string:integer) - self.tmc_messages=tmc_dict() - + self.tmc_messages=tmc_dict() + if self.writeDB: - #create new DB file - db_name=workdir+'RDS_data'+datetime.now().strftime("%Y%m%d_%H%M%S")+'.db' - db=sqlite3.connect(db_name, check_same_thread=False) - self.db=db - #create tables - try: - db.execute('''CREATE TABLE stations - (PI text PRIMARY KEY UNIQUE,PSN text, freq real, PTY text,TP integer)''') - db.execute('''CREATE TABLE groups - (time text,PI text,PSN text, grouptype text,content blob)''') - db.execute('''CREATE TABLE data - (time text,PI text,PSN text, dataType text,data blob)''') - db.execute('''CREATE TABLE grouptypeCounts - (PI text,grouptype text,count integer,unique (PI, grouptype))''') - db.execute('''CREATE TABLE TMC - (hash text PRIMARY KEY UNIQUE,time text,PI text, F integer,event integer,location integer,DP integer,div integer,dir integer,extent integer,text text,multi text,rawmgm text)''') - db.commit() - - except sqlite3.OperationalError: - print("ERROR: tables already exist") - - #self.dbc.execute('''CREATE TABLE rtp + #create new DB file + db_name=workdir+'RDS_data'+datetime.now().strftime("%Y%m%d_%H%M%S")+'.db' + db=sqlite3.connect(db_name, check_same_thread=False) + self.db=db + #create tables + try: + db.execute('''CREATE TABLE stations + (PI text PRIMARY KEY UNIQUE,PSN text, freq real, PTY text,TP integer)''') + db.execute('''CREATE TABLE groups + (time text,PI text,PSN text, grouptype text,content blob)''') + db.execute('''CREATE TABLE data + (time text,PI text,PSN text, dataType text,data blob)''') + db.execute('''CREATE TABLE grouptypeCounts + (PI text,grouptype text,count integer,unique (PI, grouptype))''') + db.execute('''CREATE TABLE TMC + (hash text PRIMARY KEY UNIQUE,time text,PI text, F integer,event integer,location integer,DP integer,div integer,dir integer,extent integer,text text,multi text,rawmgm text)''') + db.commit() + + except sqlite3.OperationalError: + print("ERROR: tables already exist") + + #self.dbc.execute('''CREATE TABLE rtp # (time text,PI text,rtp_string text)''') - reader = csv.reader(open(self.workdir+'RDS_ODA-AIDs_names_only.csv'), delimiter=',', quotechar='"') - reader.next()#skip header - for row in reader: - self.ODA_application_names[int(row[0])]=row[1] - #read location code list: - reader = csv.reader(open(self.workdir+'LCL15.1.D-160122_utf8.csv'), delimiter=';', quotechar='"') - reader.next()#skip header - self.lcl_dict=dict((int(rows[0]),rows[1:]) for rows in reader) - #read RT+ class name list: - reader = csv.reader(open(self.workdir+'RTplus_classnames.csv'), delimiter=',', quotechar='"') - reader.next()#skip header - self.rtp_classnames=dict((int(rows[0]),rows[1]) for rows in reader) - #read TMC-event list - reader = csv.reader(open(self.workdir+'event-list_with_forecast_sort.csv'), delimiter=',', quotechar='"') - reader.next()#skip header - self.ecl_dict=dict((int(rows[0]),rows[1:]) for rows in reader) - #Code,Text CEN-English,Text (German),Text (German) kein Quantifier,Text (Quantifier = 1),Text (Quantifier >1),N,Q,T,D,U,C,R ,Comment - #N:nature (blank): information, F:forecast, S:silent - #Q:quantifier type: (0..12) or blank (no quantifier) - #T:duration type: D:dynamic, L:long lasting, in brackets or if time-of-day quantifier (no 7) is used in message -> no display, only for management - #D:direction: 1:unidirectional, 2:bidirectional - #U:urgency: blank: normal, X:extremely urgent, U:urgent - #C: update class: - - #read update classes - reader = csv.reader(open(self.workdir+'tmc_update_class_names.csv'), delimiter=',', quotechar='"') - reader.next()#skip header, "code(C),english,german" - if language=="de": - self.tmc_update_class_names=dict((int(rows[0]),rows[2]) for rows in reader)#german names - else: - self.tmc_update_class_names=dict((int(rows[0]),rows[1]) for rows in reader)#english names - #read supplementary information code list - reader = csv.reader(open(self.workdir+'label6-supplementary-information-codes.csv'), delimiter=',', quotechar='"') - reader.next()#skip header, "code,english,german" - if language=="de": - self.label6_suppl_info=dict((int(rows[0]),rows[2]) for rows in reader)#german - else: - self.label6_suppl_info=dict((int(rows[0]),rows[1]) for rows in reader)#english - #read PTY list - f=open(self.workdir+'pty-list.csv') - reader = csv.reader(f, delimiter=',', quotechar='"') - reader.next()#skip header - self.pty_dict=dict((int(rows[0]),rows[1]) for rows in reader) - f.close() - self.minute_count=0 - self.minute_count_max=0 - self.minute_count_timer=time.time() - self.save_data_timer=time.time() - - atexit.register(self.goodbye) - + reader = csv.reader(open(self.workdir+'RDS_ODA-AIDs_names_only.csv'), delimiter=',', quotechar='"') + reader.next()#skip header + for row in reader: + self.ODA_application_names[int(row[0])]=row[1] + #read location code list: + reader = csv.reader(open(self.workdir+'LCL15.1.D-160122_utf8.csv'), delimiter=';', quotechar='"') + reader.next()#skip header + self.lcl_dict=dict((int(rows[0]),rows[1:]) for rows in reader) + #read RT+ class name list: + reader = csv.reader(open(self.workdir+'RTplus_classnames.csv'), delimiter=',', quotechar='"') + reader.next()#skip header + self.rtp_classnames=dict((int(rows[0]),rows[1]) for rows in reader) + #read TMC-event list + reader = csv.reader(open(self.workdir+'event-list_with_forecast_sort.csv'), delimiter=',', quotechar='"') + reader.next()#skip header + self.ecl_dict=dict((int(rows[0]),rows[1:]) for rows in reader) + #Code,Text CEN-English,Text (German),Text (German) kein Quantifier,Text (Quantifier = 1),Text (Quantifier >1),N,Q,T,D,U,C,R ,Comment + #N:nature (blank): information, F:forecast, S:silent + #Q:quantifier type: (0..12) or blank (no quantifier) + #T:duration type: D:dynamic, L:long lasting, in brackets or if time-of-day quantifier (no 7) is used in message -> no display, only for management + #D:direction: 1:unidirectional, 2:bidirectional + #U:urgency: blank: normal, X:extremely urgent, U:urgent + #C: update class: + + #read update classes + reader = csv.reader(open(self.workdir+'tmc_update_class_names.csv'), delimiter=',', quotechar='"') + reader.next()#skip header, "code(C),english,german" + if language=="de": + self.tmc_update_class_names=dict((int(rows[0]),rows[2]) for rows in reader)#german names + else: + self.tmc_update_class_names=dict((int(rows[0]),rows[1]) for rows in reader)#english names + #read supplementary information code list + reader = csv.reader(open(self.workdir+'label6-supplementary-information-codes.csv'), delimiter=',', quotechar='"') + reader.next()#skip header, "code,english,german" + if language=="de": + self.label6_suppl_info=dict((int(rows[0]),rows[2]) for rows in reader)#german + else: + self.label6_suppl_info=dict((int(rows[0]),rows[1]) for rows in reader)#english + #read PTY list + f=open(self.workdir+'pty-list.csv') + reader = csv.reader(f, delimiter=',', quotechar='"') + reader.next()#skip header + self.pty_dict=dict((int(rows[0]),rows[1]) for rows in reader) + f.close() + self.minute_count=0 + self.minute_count_max=0 + self.minute_count_timer=time.time() + self.save_data_timer=time.time() + + atexit.register(self.goodbye) + def clean_data_and_commit_db(self): - for PI in self.PI_dict: - self.PI_dict[PI]-=1 - #print(self.PI_dict) - if self.writeDB: - self.db.commit() - f=open(self.workdir+'google_maps_markers.js', 'w') - markerstring=self.tmc_messages.getMarkerString() - markerstring+='\n console.log("loaded "+markers.length+" markers")' - markerstring+='\n document.getElementById("errorid").innerHTML = "loaded "+markers.length+" markers";' - f.write(markerstring) - f.close() + for PI in self.PI_dict: + self.PI_dict[PI]-=1 + #print(self.PI_dict) + if self.writeDB: + self.db.commit() + f=open(self.workdir+'google_maps_markers.js', 'w') + markerstring=self.tmc_messages.getMarkerString() + markerstring+='\n console.log("loaded "+markers.length+" markers")' + markerstring+='\n document.getElementById("errorid").innerHTML = "loaded "+markers.length+" markers";' + f.write(markerstring) + f.close() def update_freq(self): - # " " is a tab character - message_string="decoder frequencies:" - for num in self.decoder_frequencies: - freq=self.decoder_frequencies[num] - if self.decoders[num]['synced']: - message_string+="  %i:%0.1fM"% (num,freq/1e6) - #print("'color:green'>%i:%0.1fM"% (num,freq/1e6)) - else:#elif self.decoders[num]['synced']==False: - #print("'color:red'>%i:%0.1fM"% (num,freq/1e6)) - message_string+="  %i:%0.1fM"% (num,freq/1e6) - message_string+="  tuned frequency:%0.1fM"%(self.tuning_frequency/1e6) - self.signals.DataUpdateEvent.emit({'decoder_frequencies':message_string}) - #print(message_string) - #self.signals.DataUpdateEvent.emit({'row':decoder_num,'freq':freq_str}) - #print("nr:%i freq:%s"%(tgtnum,freq_str)) + # " " is a tab character + message_string="decoder frequencies:" + for num in self.decoder_frequencies: + freq=self.decoder_frequencies[num] + if self.decoders[num]['synced']: + message_string+="  %i:%0.1fM"% (num,freq/1e6) + #print("'color:green'>%i:%0.1fM"% (num,freq/1e6)) + else:#elif self.decoders[num]['synced']==False: + #print("'color:red'>%i:%0.1fM"% (num,freq/1e6)) + message_string+="  %i:%0.1fM"% (num,freq/1e6) + message_string+="  tuned frequency:%0.1fM"%(self.tuning_frequency/1e6) + self.signals.DataUpdateEvent.emit({'decoder_frequencies':message_string}) + #print(message_string) + #self.signals.DataUpdateEvent.emit({'row':decoder_num,'freq':freq_str}) + #print("nr:%i freq:%s"%(tgtnum,freq_str)) def set_freq_tune(self,freq): - self.tuning_frequency=int(freq) - self.update_freq() + self.tuning_frequency=int(freq) + self.update_freq() def set_freq(self,msg): - m = pmt.symbol_to_string(msg) - decoder_num=int(m.split()[0])-1#msgs are 1-indexed, decoder_num is 0-indexed - freq_str=m.split()[1] - try: - freq=float(freq_str) - self.decoder_frequencies[decoder_num]=freq - freq_str="%i:%0.1fM"% (decoder_num,freq/1e6) - except ValueError: - pass#leave string as is - self.update_freq() + m = pmt.symbol_to_string(msg) + decoder_num=int(m.split()[0])-1#msgs are 1-indexed, decoder_num is 0-indexed + freq_str=m.split()[1] + try: + freq=float(freq_str) + self.decoder_frequencies[decoder_num]=freq + freq_str="%i:%0.1fM"% (decoder_num,freq/1e6) + except ValueError: + pass#leave string as is + self.update_freq() def init_data_for_PI(self,PI): - self.RDS_data[PI]={} - #self.RDS_data[PI]["blockcounts"]={}# no defaults (works aswell) - #defaults are to keep colors in piechart consistent between stations: - - coverage_area={"0":"local,","1":"international,","2":"national,","3":"supranational,"} - pi_str="" - if list(PI)[1] in "456789ABCDEF": + self.RDS_data[PI]={} + #self.RDS_data[PI]["blockcounts"]={}# no defaults (works aswell) + #defaults are to keep colors in piechart consistent between stations: + + coverage_area={"0":"local,","1":"international,","2":"national,","3":"supranational,"} + pi_str="" + if list(PI)[1] in "456789ABCDEF": pi_str+="regional, " - else: + else: pi_str+=coverage_area[list(PI)[1]] - bundeslaender={"0":"Baden-Württemberg","1":"Bayern","2":"Berlin","3":"Brandenburg","4":"Bremen und Bremerhaven", - "5":"Hamburg","6":"Hessen","7":"Mecklenburg-Vorpommern","8":"Niedersachsen","9":"Nordrhein-Westfalen","A":"Rheinland-Pfalz", - "B":"Saarland","C":"Sachsen","D":"Sachsen-Anhalt","E":"Schleswig-Holstein","F":"Thüringen"} - if list(PI)[0] in "D1": + bundeslaender={"0":"Baden-Württemberg","1":"Bayern","2":"Berlin","3":"Brandenburg","4":"Bremen und Bremerhaven", + "5":"Hamburg","6":"Hessen","7":"Mecklenburg-Vorpommern","8":"Niedersachsen","9":"Nordrhein-Westfalen","A":"Rheinland-Pfalz", + "B":"Saarland","C":"Sachsen","D":"Sachsen-Anhalt","E":"Schleswig-Holstein","F":"Thüringen"} + if list(PI)[0] in "D1": pi_str+="germany?, " pi_str+=bundeslaender[list(PI)[2]]+", " pi_str+="NR:"+list(PI)[3] - self.RDS_data[PI]["CC"]=list(PI)[0] - self.RDS_data[PI]["PI-meaning"]=pi_str - self.RDS_data[PI]["blockcounts"]={"0A":0,"1A":0,"2A":0,"3A":0,"4A":0,"6A":0,"8A":0,"12A":0,"14A":0} - self.RDS_data[PI]["blockcounts"]["any"]=0 - self.RDS_data[PI]["AID_list"]={} - self.RDS_data[PI]["PSN"]="_"*8 - self.RDS_data[PI]["PSN_valid"]=[False]*8 - self.RDS_data[PI]["AF"]={"set":set(),"rxset":set()} - self.RDS_data[PI]["TP"]=-1 - self.RDS_data[PI]["TA"]=-1 - self.RDS_data[PI]["PTY"]="" - self.RDS_data[PI]["DI"]=[2,2,2,2] - self.RDS_data[PI]["internals"]={"last_rt_tooltip":"","unfinished_TMC":{},"last_valid_rt":"","last_valid_psn":"","RT_history":[]} - self.RDS_data[PI]["time"]={"timestring":"88:88","datestring":"00-00-0000","datetime":None} + self.RDS_data[PI]["CC"]=list(PI)[0] + self.RDS_data[PI]["PI-meaning"]=pi_str + self.RDS_data[PI]["blockcounts"]={"0A":0,"1A":0,"2A":0,"3A":0,"4A":0,"6A":0,"8A":0,"12A":0,"14A":0} + self.RDS_data[PI]["blockcounts"]["any"]=0 + self.RDS_data[PI]["AID_list"]={} + self.RDS_data[PI]["PSN"]="_"*8 + self.RDS_data[PI]["PSN_valid"]=[False]*8 + self.RDS_data[PI]["AF"]={"set":set(),"rxset":set()} + self.RDS_data[PI]["TP"]=-1 + self.RDS_data[PI]["TA"]=-1 + self.RDS_data[PI]["PTY"]="" + self.RDS_data[PI]["DI"]=[2,2,2,2] + self.RDS_data[PI]["internals"]={"last_rt_tooltip":"","unfinished_TMC":{},"last_valid_rt":"","last_valid_psn":"","RT_history":[]} + self.RDS_data[PI]["time"]={"timestring":"88:88","datestring":"00-00-0000","datetime":None} def handle_msg(self, msg, port):#port from 0 to 3 - if pmt.to_long(pmt.car(msg))==1L: - data=pmt.to_python(pmt.cdr(msg)) - #print("port:%i, data: %s"%(port,data)) - self.decoders[port]['synced']=data - self.update_freq() - else: #elif pmt.to_long(pmt.car(msg))==0L - array=pmt.to_python(msg)[1] - - if time.time()-self.save_data_timer > 10:#every 10 seconds - self.save_data_timer=time.time() - self.clean_data_and_commit_db() - - if time.time()-self.minute_count_timer > 3:#every 3 second - self.minute_count_max=self.minute_count - self.signals.DataUpdateEvent.emit({'group_count':self.minute_count,'group_count_max':self.minute_count_max}) - self.minute_count=0 - self.minute_count_timer=time.time() - #pr.enable()#disabled-internal-profiling - self.minute_count+=1 - #self.signals.DataUpdateEvent.emit({'group_count':self.minute_count,'group_count_max':self.minute_count_max}) - if self.writeDB: - #db=sqlite3.connect(self.db_name) - db=self.db - - - groupNR=array[2]&0b11110000 - groupVar=array[2]&0b00001000 - if (groupVar == 0): - groupType=str(groupNR >> 4)+"A" - else: - groupType=str(groupNR >> 4)+"B" - #if self.debug: - #PI=str(port)+"_%02X%02X" %(array[0],array[1]) - #else: - #PI="%02X%02X" %(array[0],array[1]) - PI="%02X%02X" %(array[0],array[1]) - TP=(array[2]>>2)&0x1 - block2=(array[2]<<8)|(array[3]) #block2 - PTY=(block2>>5)&0x1F - wrong_blocks=int(array[12]) - - try: - self.PI_dict[PI]+=1 - except KeyError: - pass - - if not self.RDS_data.has_key(PI):#station invalid/new - if not self.PI_dict.has_key(PI):#1st group - self.PI_dict[PI]=1 - return#dont decode further if not yet valid - elif self.PI_dict[PI]>2:#count station as valid if more than 2 packets received - self.init_data_for_PI(PI)#initialize dict for station - if self.log: - print("found station %s"%PI) - else: - return#dont decode further if not yet valid - - if self.decoder_frequencies.has_key(port): - freq=self.decoder_frequencies[port] - freq_str="%i:%0.1fM"% (port,freq/1e6) - self.RDS_data[PI]["tuned_freq"]=freq - #self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) - if self.RDS_data[PI]["blockcounts"].has_key(groupType): - self.RDS_data[PI]["blockcounts"][groupType] +=1 #increment - else: - self.RDS_data[PI]["blockcounts"][groupType] = 1 #initialize (1st group of this type) - self.RDS_data[PI]["blockcounts"]["any"]+=1 - if self.RDS_data[PI]["blockcounts"]["any"]==5: - self.RDS_data[PI]["blockcounts"]["any"]=0 - if self.writeDB: - t=(str(PI),groupType,self.RDS_data[PI]["blockcounts"][groupType])#TODO only update DB every few seconds - db.execute("INSERT OR REPLACE INTO grouptypeCounts (PI,grouptype,count) VALUES (?,?,?)",t) - dots="."*self.RDS_data[PI]["blockcounts"]["any"] - self.RDS_data[PI]["TP"]=TP - self.RDS_data[PI]["PTY"]=self.pty_dict[PTY] - - self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'PTY':self.pty_dict[PTY],'TP':TP,'wrong_blocks':wrong_blocks,'dots':dots}) - - - - #add any received groups to DB (slow) - #content="%02X%02X%02X%02X%02X" %(array[3]&0x1f,array[4],array[5],array[6],array[7]) - #t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],groupType,content) - #db.execute("INSERT INTO groups VALUES (?,?,?,?,?)",t) - - if (groupType == "0A"):#AF PSN - adr=array[3]&0b00000011 - segment=self.decode_chars(chr(array[6])+chr(array[7])) - d=(array[3]>>2)&0x1 - self.RDS_data[PI]["DI"][3-adr]=d#decoder information - #DI[0]=d0 0=Mono 1=Stereo - #d1 Not artificial head Artificial head - #d2 Not compressed Compressed - #d3 Static PTY Dynamic PTY - TA=(array[3]>>4)&0x1 - MS=(array[3]>>3)&0x1 - self.RDS_data[PI]["TA"]=TA - #style='font-family:Courier New;color:%s' - flag_string="TP:%i, TA:%i, MS:%i, DI:%s"%(TP,TA,MS,str(self.RDS_data[PI]["DI"])) - pty_colored=self.RDS_data[PI]["PTY"] - if TP==1: - if TA==1: - color="red" - elif TA==0: - color="green" - else: - color="yellow" - pty_colored="%s"%(color,self.RDS_data[PI]["PTY"]) - - self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'flags':flag_string,'PTY':pty_colored}) - - #224 1110 0000 = no AF - #225 1110 0001 = 1AF - #249 1111 1001 = 25AF - fillercode=205#1100 1101 - if not self.RDS_data[PI]["AF"].has_key("main") and self.RDS_data[PI].has_key("tuned_freq"): - #if self.RDS_data[PI].has_key("tuned_freq"):#update main freq even if one exists -> DB problem - freq=self.decode_AF_freq(array[4]) - if freq==self.RDS_data[PI]["tuned_freq"]: - self.RDS_data[PI]["AF"]["main"]=freq - if self.log: - print("main frequency found in 0A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq/1e6)) - freq_str="0A:%0.1fM"% (freq/1e6) - self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) - t=(PI,self.RDS_data[PI]["PSN"],float(freq),self.RDS_data[PI]["PTY"],int(self.RDS_data[PI]["TP"])) - if self.writeDB: - db.execute("INSERT INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t) - freq=self.decode_AF_freq(array[5]) - if freq==self.RDS_data[PI]["tuned_freq"]: - self.RDS_data[PI]["AF"]["main"]=freq - if self.log: - print("main frequency found in 0A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq/1e6)) - freq_str="0A:%0.1fM"% (freq/1e6) - self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) - t=(PI,self.RDS_data[PI]["PSN"],float(freq),self.RDS_data[PI]["PTY"],int(self.RDS_data[PI]["TP"])) - if self.writeDB: - db.execute("INSERT INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t) - if self.RDS_data[PI].has_key("tuned_freq") :#TODO add secondary freqs - freq=self.decode_AF_freq(array[4]) - diff=abs(freq-self.RDS_data[PI]["tuned_freq"]) - if diff<100000: - self.RDS_data[PI]["AF"]["rxset"].add(freq) - freq=self.decode_AF_freq(array[5]) - diff=abs(freq-self.RDS_data[PI]["tuned_freq"]) - if diff<100000: - self.RDS_data[PI]["AF"]["rxset"].add(freq) - - if(array[4]>= 224 and array[4]<= 249): - #print("AF1 detected") - self.RDS_data[PI]["AF"]['number']=array[4]-224 - #self.RDS_data[PI]["AF"]['main']=self.decode_AF_freq(array[5]) - self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'AF':self.RDS_data[PI]["AF"]}) - if(array[5]>= 224 and array[5]<= 249): - print("AF2 detected (shouldn't happen) %s"%array[5]) - - - #add frequencies to set - self.RDS_data[PI]["AF"]["set"].add(self.decode_AF_freq(array[4])) - self.RDS_data[PI]["AF"]["set"].add(self.decode_AF_freq(array[5])) - try: - self.RDS_data[PI]["AF"]["set"].remove(0)#remove control characters - except KeyError: - pass - - name_list=list(self.RDS_data[PI]["PSN"]) - if (name_list[adr*2:adr*2+2]==list(segment)):#segment already there - segmentcolor="green" - elif(name_list[adr*2:adr*2+2]==['_']*2): #segment new - segmentcolor="orange" - name_list[adr*2:adr*2+2]=segment - else:#name changed (böse) - segmentcolor="red" - name_list=['_']*8 #reset name - name_list[adr*2:adr*2+2]=segment - #reset stored text: - self.RDS_data[PI]["PSN"]="_"*8 - self.RDS_data[PI]["PSN_valid"]=[False]*8 - self.RDS_data[PI]["PSN_valid"][adr*2:adr*2+2]=[True] *2 - self.RDS_data[PI]["PSN"]="".join(name_list) - #determine if text is valid - valid=True - for i in range(0,8): - if (not self.RDS_data[PI]["PSN_valid"][i]): - valid = False - if(valid): - #textcolor="black" - textcolor=""#use default color (white if background is black) - if not self.RDS_data[PI]["internals"]["last_valid_psn"]==self.RDS_data[PI]["PSN"]:#ignore duplicates - t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"PSN_valid",self.RDS_data[PI]["PSN"]) - if self.writeDB: - db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) - t=(self.RDS_data[PI]["PSN"],PI) - if self.writeDB: - db.execute("UPDATE OR IGNORE stations SET PSN=? WHERE PI IS ?",t) - self.RDS_data[PI]["internals"]["last_valid_psn"]=self.RDS_data[PI]["PSN"] - else: - textcolor="gray" - formatted_text=self.color_text(self.RDS_data[PI]["PSN"],adr*2,adr*2+2,textcolor,segmentcolor) - self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'PSN':formatted_text}) - elif (groupType == "1A"):#PIN programme item number - #wer nutzt 1A gruppen? - #antenne1: variants: 0(ECC), - PIN=(array[6]<<8)|(array[7]) - SLC=(array[4]<<8)|(array[5])&0xfff#slow labeling code - radio_paging=array[3]&0x1f - LA=array[4]>>7#linkage actuator - variant=(array[4]>>4)&0x7 - PIN_day=(PIN>>11)&0x1f - PIN_hour=(PIN>>6)&0x1f - PIN_minute=PIN&0x3f - PIN_valid= PIN_day in range(1,32) and PIN_hour in range(0,24) and PIN_minute in range(0,60) - if PIN_valid: - self.RDS_data[PI]["PIN"]=[PIN_day,PIN_hour,PIN_minute] - data_string="variant:%i,SLC:%04X,PIN (valid):%s "%(variant,SLC,str([PIN_day,PIN_hour,PIN_minute])) - else: - data_string="variant:%i,SLC:%04X,PIN:%04X "%(variant,SLC,PIN) - #%02X%02X%02X%02X%02X - - t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"PIN",data_string) - if self.writeDB: - db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) - if self.debug and not variant==0:#print if not seen before - print("PI:%s PSN:%s uses variant %i of 1A"%(PI,self.RDS_data[PI]["PSN"],variant)) - if variant==0: - paging=array[4]&0xf - extended_country_code=array[5] - self.RDS_data[PI]["ECC"]=extended_country_code - if self.debug: - print("1A variant 0: PI:%s PSN:%s,ECC:%s"%(PI,self.RDS_data[PI]["PSN"],hex(extended_country_code))) - elif variant==1: - TMC_info=SLC - elif variant==2: - paging_info=SLC - elif variant==3: - language_codes=SLC - if self.debug: - print("PI:%s PSN:%s,language_codes:%s"%(PI,self.RDS_data[PI]["PSN"],hex(language_codes))) - elif variant==6: - #for use by broadcasters - if self.debug: - print("PI:%s PSN:%s uses variant 6 of 1A"%(PI,self.RDS_data[PI]["PSN"])) - elif variant==7: - ESW_channel_identification=SLC - #end of 1A decode - elif (groupType == "2A"):#RT radiotext - if(not self.RDS_data[PI].has_key("RT_0")):#initialize variables - self.RDS_data[PI]["RT_0"]={"RT":"_"*64,"RT_valid":[False]*64,"RT_all_valid":False} - self.RDS_data[PI]["RT_1"]={"RT":"_"*64,"RT_valid":[False]*64,"RT_all_valid":False} - #self.RDS_data[PI]["RT"]="_"*64 - #self.RDS_data[PI]["RT_valid"]=[False]*64 - #self.RDS_data[PI]["RT_all_valid"]=False - self.RDS_data[PI]["RT_last_ab_flag"]=2 - - adr= array[3]&0b00001111 - ab_flag=(array[3]&0b00010000)>>4 - #print("PI:%s, AB:%i"%(PI,ab_flag)) - - - #if self.RDS_data[PI]["RT_last_ab_flag"] !=ab_flag:#AB flag changed -> clear text - # self.RDS_data[PI]["RT"]="_"*64 - # self.RDS_data[PI]["RT_valid"]=[False]*64 - # self.RDS_data[PI]["RT_last_ab_flag"] =ab_flag - self.RDS_data[PI]["RT_last_ab_flag"] =ab_flag - - #segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7])) - segment=chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7])#EDIT:latedecode - - #print("RT:adress: %d, segment:%s"%(adr,segment)) - #self.signals.DataUpdateEvent.emit({'col':5,'row':port,'PI':PI,'groupType':groupType,'adress':adr,'segment':segment}) - text_list=list(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]) - #determine text length: - try: - text_end=text_list.index('\r') - except ValueError: - text_end=64 #assume whole string is important - pass - predicted=False - if (text_list[adr*4:adr*4+4]==list(segment)):#segment already there - segmentcolor="green" - elif (text_list[adr*4:adr*4+4]==['_']*4):#segment new - segmentcolor="orange" - text_list[adr*4:adr*4+4]=segment - else: - segmentcolor="red" - text_list=['_']*64 #clear text - text_list[adr*4:adr*4+4]=segment - #reset stored text: - self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="_"*64 - self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"]=[False]*64 - #predict RT from last texts: - for rt in self.RDS_data[PI]["internals"]["RT_history"]: - if rt[adr*4:adr*4+4]==list(segment): - self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="".join(rt) - predicted=True - - self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"][adr*4:adr*4+4]=[True] *4 - if not predicted: - self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="".join(text_list) - - #determine if (new) text is valid - self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"]=True - for i in range(0,text_end): - if (not self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"][i]): - self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"] = False - if(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"]): - #textcolor="black" - textcolor=""#use default color (white if background is black) - l=list(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]) - rt="".join(l[0:text_end])#remove underscores(default symbol) after line end marker - if not self.RDS_data[PI]["internals"]["last_valid_rt"]==rt:#ignore duplicates #TODO add 2nd order duplicates ABAB - self.RDS_data[PI]["internals"]["RT_history"].append(l) - if len(self.RDS_data[PI]["internals"]["RT_history"])>10:#only store last 10 RTs - self.RDS_data[PI]["internals"]["RT_history"].pop(0) - if self.writeDB: - t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT",self.decode_chars(rt)) - db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) - self.RDS_data[PI]["internals"]["last_valid_rt"]=rt - try:#save rt+ if it exist - if self.writeDB: - t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT+",self.decode_chars(str(self.RDS_data[PI]["RT+"]))) - db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) - except KeyError: - pass#no rt+ -> dont save - else: - textcolor="gray" - display_text=self.decode_chars(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"].split("\r")[0]) - formatted_text=self.color_text(display_text,adr*4,adr*4+4,textcolor,segmentcolor) - rtcol=self.colorder.index('text') - self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'string':formatted_text}) - - elif (groupType == "3A"):#ODA announcements (contain application ID "AID") - AID=(array[6]<<8)|(array[7])#combine 2 bytes into 1 block - app_data=(array[4]<<8)|(array[5])#content defined by ODA-app - app_group_raw=array[3]&0x1f #group type in which this app is sent - if (app_group_raw&0x1 == 0): - app_group=str(app_group_raw >> 1)+"A" - else: - app_group=str(app_group_raw >> 1)+"B" - - if not self.RDS_data[PI]["AID_list"].has_key(AID):#new ODA found - try: - app_name=self.ODA_application_names[AID] - except KeyError: - if self.debug: - print("ERROR: ODA-app-id (AID) '%i' not found in list on station %s, app group:%s"%(AID,app_group,PI)) - app_name="unknown" - self.RDS_data[PI]["AID_list"][AID]={} - self.RDS_data[PI]["AID_list"][AID]["groupType"]=app_group - self.RDS_data[PI]["AID_list"][AID]["app_name"]=app_name - self.RDS_data[PI]["AID_list"][AID]["app_data"]=app_data - if AID==52550:#TMC alert-c initialize - self.RDS_data[PI]["AID_list"][AID]["provider name"]="________" - if self.log: - print("new ODA: AID:%i, name:'%s', app_group:%s, station:%s" %(AID,app_name,app_group,PI)) - #decode 3A group of TMC - if AID==52550:#TMC alert-c (continuously update) - variant=app_data>>14 - if variant==0: - self.RDS_data[PI]["AID_list"][AID]["LTN"]=(app_data>>6)&0x3f#location table number (6 bits) - self.RDS_data[PI]["AID_list"][AID]["AFI"]=(app_data>>5)&0x1#alternative frequency indicator - self.RDS_data[PI]["AID_list"][AID]["M"]=(app_data>>4)&0x1#transmission mode indicator - #Message Geographical Scope: - self.RDS_data[PI]["AID_list"][AID]["scope"]="" - if (app_data>>3)&0x1==1: - self.RDS_data[PI]["AID_list"][AID]["scope"]+="I"#international (EUROROAD) - if (app_data>>2)&0x1==1: - self.RDS_data[PI]["AID_list"][AID]["scope"]+="N"#national - if (app_data>>1)&0x1==1: - self.RDS_data[PI]["AID_list"][AID]["scope"]+="R"#regional - if (app_data>>0)&0x1==1: - self.RDS_data[PI]["AID_list"][AID]["scope"]+="U"#urban - #self.RDS_data[PI]["AID_list"][AID]["I"]=(app_data>>3)&0x1#international (EUROROAD) - #self.RDS_data[PI]["AID_list"][AID]["N"]=(app_data>>2)&0x1#national - #self.RDS_data[PI]["AID_list"][AID]["R"]=(app_data>>1)&0x1#regional - #self.RDS_data[PI]["AID_list"][AID]["U"]=(app_data>>0)&0x1#urban - elif variant==1: - self.RDS_data[PI]["AID_list"][AID]["SID"]=(app_data>>6)&0x3f#service identifier - #timing parameters (used to switch away from TMC station without missing messages): - self.RDS_data[PI]["AID_list"][AID]["G"]=(app_data>>12)&0x3#gap parameter - self.RDS_data[PI]["AID_list"][AID]["activity_time"]=(app_data>>4)&0x3 - self.RDS_data[PI]["AID_list"][AID]["window_time"]=(app_data>>2)&0x3 - self.RDS_data[PI]["AID_list"][AID]["delay_time"]=(app_data>>0)&0x3 - elif self.debug: - print("unknown variant %i in TMC 3A group"%variant) - elif (groupType == "4A"):#CT clock time - bits=BitArray('uint:8=%i,uint:8=%i,uint:8=%i,uint:8=%i,uint:8=%i'%tuple(array[3:8])) - spare,datecode,hours,minutes,offsetdir,local_time_offset = bits.unpack("uint:6,uint:17,uint:5,uint:6,uint:1,uint:5") - local_time_offset*=0.5 - #datecode=((array[3] & 0x03) << 15) | (array[4] <<7)|((array[5] >> 1) & 0x7f)#modified julian date - if datecode==0: - #do not update!! - if self.debug: - print("station:%s sent empty 4A group"%self.RDS_data[PI]["PSN"]) - else: - #hours=((array[5] & 0x1) << 4) | ((array[6] >> 4) & 0x0f) - #minutes=((array[6] &0x0F)<<2)|((array[7] >>6)&0x3) - #offsetdir=(array[7]>>5)&0x1 - #local_time_offset=0.5*((array[7])&0x1F) - if(offsetdir==1): - local_time_offset*=-1 - - date=datetime(1858,11,17)+timedelta(days=int(datecode))#convert from MJD (modified julian date) - - timestring="%02i:%02i (%+.1fh)" % (hours,minutes,local_time_offset) - datestring=date.strftime("%d.%m.%Y") - ctcol=self.colorder.index('time') - self.signals.DataUpdateEvent.emit({'col':ctcol,'row':port,'PI':PI,'string':timestring,'tooltip':datestring}) - t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"CT",datestring+" "+timestring+"; datecode(MJD):"+str(datecode)) - self.RDS_data[PI]["time"]["timestring"]=timestring - self.RDS_data[PI]["time"]["datestring"]=datestring - try: - self.RDS_data[PI]["time"]["datetime"]=datetime(date.year,date.month,date.day,hours,minutes)+timedelta(hours=local_time_offset) - except ValueError: - print("ERROR: could not interpret time or date:"+datestring+" "+timestring) - if self.writeDB: - db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) - elif (groupType == "6A"):#IH inhouse data -> save for analysis - """In House Data: -{'130A': {'1E1077FFFF': {'count': 1, - 'last_time': '2016-12-08 16:26:54.767596'}, - '1F23022015': {'count': 1, - 'last_time': '2016-12-08 16:26:56.341271'}}, - 'D00F': {'1E1023FFFF': {'count': 1, - 'last_time': '2016-12-08 16:26:54.769165'}, - '1F28032008': {'count': 3, - 'last_time': '2016-12-08 16:26:58.272420'}}}""" - ih_data="%02X%02X%02X%02X%02X" %(array[3]&0x1f,array[4],array[5],array[6],array[7]) - if not self.IH_data.has_key(PI): - self.IH_data[PI]={} - if not self.IH_data[PI].has_key(ih_data): - self.IH_data[PI][ih_data]={} - self.IH_data[PI][ih_data]["count"]=0 - self.IH_data[PI][ih_data]["count"]+=1 - self.IH_data[PI][ih_data]["last_time"]=str(datetime.now()) - #TMC-alert-c (grouptype mostly 8A): - elif self.RDS_data[PI]["AID_list"].has_key(52550) and self.RDS_data[PI]["AID_list"][52550]["groupType"]==groupType:#TMC alert-C - tmc_x=array[3]&0x1f #lower 5 bit of block2 - tmc_y=(array[4]<<8)|(array[5]) #block3 - tmc_z=(array[6]<<8)|(array[7])#block4 - tmc_hash=md5.new(str([PI,tmc_x,tmc_y,tmc_z])).hexdigest() - tmc_T=tmc_x>>4 #0:TMC-message 1:tuning info/service provider name - tmc_F=int((tmc_x>>3)&0x1) #identifies the message as a Single Group (F = 1) or Multi Group (F = 0) - Y15=int(tmc_y>>15) - #timestring=self.RDS_data[PI]["time"]["timestring"] - datetime_received=self.RDS_data[PI]["time"]["datetime"] - if tmc_T == 0: - if tmc_F==1:#single group - tmc_msg=tmc_message(PI,tmc_x,tmc_y,tmc_z,datetime_received,self) - self.print_tmc_msg(tmc_msg) - elif tmc_F==0 and Y15==1:#1st group of multigroup - ci=int(tmc_x&0x7) - tmc_msg=tmc_message(PI,tmc_x,tmc_y,tmc_z,datetime_received,self) - #if self.RDS_data[PI]["internals"]["unfinished_TMC"].has_key(ci): - #print("overwriting parital message") - self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]={"msg":tmc_msg,"time":time.time()} - else: - ci=int(tmc_x&0x7) - if self.RDS_data[PI]["internals"]["unfinished_TMC"].has_key(ci): - tmc_msg=self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["msg"] - tmc_msg.add_group(tmc_y,tmc_z) - age=time.time()-self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["time"] - t=(time.time(),PI,age,ci,tmc_msg.is_complete) - #print("%f: continuing message PI:%s,age:%f,ci:%i complete:%i"%t) - self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["time"]=time.time() - if tmc_msg.is_complete: - self.print_tmc_msg(tmc_msg)#print and store message - del self.RDS_data[PI]["internals"]["unfinished_TMC"][tmc_msg.ci]#delete finished message - else: - #if not ci==0: - #print("ci %i not found, discarding"%ci) - pass - - else:#alert plus or provider info - adr=tmc_x&0xf - if 4 <= adr and adr <= 9: - #seen variants 4569, 6 most often - #print("TMC-info variant:%i"%adr) - if adr==4 or adr==5:#service provider name - segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7])) - if self.debug: - print("TMC-info adr:%i (provider name), segment:%s, station:%s"%(adr,segment,self.RDS_data[PI]["PSN"])) - if self.RDS_data[PI]["AID_list"].has_key(52550): - text_list=list(self.RDS_data[PI]["AID_list"][52550]["provider name"]) - seg_adr_start=(adr-4)*4#start of segment - text_list[seg_adr_start:seg_adr_start+4]=segment - self.RDS_data[PI]["AID_list"][52550]["provider name"]="".join(text_list) - if adr== 7:#freq of tuned an mapped station (not seen yet) - freq_TN=tmc_y>>8 - freq_ON=tmc_y&0xff#mapped frequency - if self.debug: - print("TMC-info: TN:%i, station:%s"%(freq_TN,self.RDS_data[PI]["PSN"])) - self.RDS_data[PI]["TMC_TN"]=freq_TN - else: - if self.debug: - print("alert plus on station %s (%s)"%(PI,self.RDS_data[PI]["PSN"]))#(not seen yet) - - #self.tableobj.RDS_data["D301"]["AID_list"][52550]["provider name"]="test____" - #RadioText+ (grouptype mostly 12A): - elif self.RDS_data[PI]["AID_list"].has_key(19415) and self.RDS_data[PI]["AID_list"][19415]["groupType"]==groupType:#RT+ - if not self.RDS_data[PI].has_key("RT+"): - #self.RDS_data[PI]["RT+"]={"history":{},"last_item_toggle_bit":2} - self.RDS_data[PI]["RT+"]={"last_item_toggle_bit":2} - self.RDS_data[PI]["RT+_history"]={} - self.RDS_data[PI]["internals"]["RT+_times"]={} - #self.RDS_data[PI]["RT+"]["last_item_toggle_bit"]=2 - A3_data=self.RDS_data[PI]["AID_list"][19415]["app_data"] - template_number=A3_data&0xff - SCB=(A3_data >> 8)&0x0f#server control bit - CB_flag=(A3_data>>12)&0x1 #is set if template available - rtp_message= ((array[3]&0x1f)<<32)|(array[4]<<24)|(array[5]<<16)|(array[6]<<8)|(array[7]) - item_toggle_bit=(rtp_message>>36)&0x1 - item_running_bit=(rtp_message>>35)&0x1 - tag1=(rtp_message>>17)&(2**18-1)#6+6+6 - tag2=(rtp_message)&(2**17-1)#6+6+5 - tag1_type=self.rtp_classnames[int(tag1>>12)] - tag2_type=self.rtp_classnames[int(tag2>>11)] - tag1_start=int((tag1>>6)&(2**6-1)) - tag1_len=int(tag1&(2**6-1)) - tag2_start=int((tag2>>5)&(2**6-1)) - tag2_len=int(tag2&(2**5-1)) - if not self.RDS_data[PI]["RT+"]["last_item_toggle_bit"] == item_toggle_bit: #new item - #self.RDS_data[PI]["RT+"]["history"][str(datetime.now())]=self.RDS_data[PI]["internals"]["last_rt_tooltip"] - t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT+",str(self.RDS_data[PI]["RT+"])) - if self.writeDB: - db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) - self.RDS_data[PI]["RT+_history"][str(datetime.now())]=copy.deepcopy(self.RDS_data[PI]["RT+"])#save old item - self.RDS_data[PI]["RT+"]["last_item_toggle_bit"] = item_toggle_bit - rtcol=self.colorder.index('text') - if self.debug: - print("toggle bit changed on PI:%s, cleared RT-tt"%PI) - self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'tooltip':""}) - if self.RDS_data[PI].has_key("RT_0"): - ab_flag=self.RDS_data[PI]["RT_last_ab_flag"] - rt=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"] - rt_valid=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"] - if not tag1_type=="DUMMY_CLASS" and all(rt_valid[tag1_start:tag1_start+tag1_len+1]): - self.RDS_data[PI]["RT+"][tag1_type]=rt[tag1_start:tag1_start+tag1_len+1] - self.RDS_data[PI]["internals"]["RT+_times"][tag1_type]=time.time() - if not tag2_type=="DUMMY_CLASS" and all(rt_valid[tag2_start:tag2_start+tag2_len+1]): - self.RDS_data[PI]["RT+"][tag2_type]=rt[tag2_start:tag2_start+tag2_len+1] - self.RDS_data[PI]["internals"]["RT+_times"][tag2_type]=time.time() - #check outdated tags: - for tagtype in self.RDS_data[PI]["internals"]["RT+_times"].keys():#.keys() makes copy to avoid RuntimeError: dictionary changed size during iteration - age=time.time()-self.RDS_data[PI]["internals"]["RT+_times"][tagtype] - if age>90:#delete if older than 90 sek#TODO delete if toggle bit changes?, delete if tag changes? (title change -> delete artist) - del self.RDS_data[PI]["internals"]["RT+_times"][tagtype] - del self.RDS_data[PI]["RT+"][tagtype] - - - tags="ir:%i,it:%i"%(item_running_bit,item_toggle_bit) - rtpcol=self.colorder.index('RT+') - self.signals.DataUpdateEvent.emit({'col':rtpcol,'row':port,'PI':PI,'string':tags}) - if(tag2_type=="ITEM.TITLE" and self.RDS_data[PI].has_key("RT_0")):#TODO remove duplicate code - ab_flag=self.RDS_data[PI]["RT_last_ab_flag"] - rt=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"] - rt_valid=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"] - artist="?" - song="?" - if all(rt_valid[tag1_start:tag1_start+tag1_len+1]): - artist=rt[tag1_start:tag1_start+tag1_len+1] - if all(rt_valid[tag2_start:tag2_start+tag2_len+1]): - song=rt[tag2_start:tag2_start+tag2_len+1] - formatted_text="%s by %s"%(song,artist) - rtcol=self.colorder.index('text') - #only update tooltip if text changed -> remove flicker, still flickers :( - if not formatted_text == self.RDS_data[PI]["internals"]["last_rt_tooltip"]: - self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'tooltip':formatted_text}) - self.RDS_data[PI]["internals"]["last_rt_tooltip"] = formatted_text - #elif(not tag1_type=="ITEM.ARTIST" and not tag1_type=="DUMMY_CLASS"): - # print("%s:RT+: tag1_type:%s, tag2_type:%s"%(PI,tag1_type,tag2_type)) - elif (groupType == "14A"):#EON enhanced other networks - #TN = tuned network, ON=other network - if not self.RDS_data[PI].has_key("EON"): - self.RDS_data[PI]["EON"]={} - TP_ON=(array[3]>>4)&0x1 - PI_ON="%02X%02X" %(array[6],array[7]) - variant=array[3]&0xf - self.signals.DataUpdateEvent.emit({'PI':PI_ON,'TP':TP_ON}) - if not self.RDS_data.has_key(PI_ON): - self.init_data_for_PI(PI_ON) - self.RDS_data[PI_ON]["TP"]=TP_ON - self.PI_dict[PI_ON]=0#initialize dict, even if no packets received - if self.log: - print("found station %s via EON on station %s"%(PI_ON,PI)) - if not self.RDS_data[PI]["EON"].has_key(PI_ON): - self.RDS_data[PI]["EON"][PI_ON]={} - self.RDS_data[PI]["EON"][PI_ON]["PSN"]="_"*8 - if variant in range(4):#variant 0..3 -> PS_ON - segment=self.decode_chars(chr(array[4])+chr(array[5])) - name_list=list(self.RDS_data[PI_ON]["PSN"]) - #name_list=list(self.RDS_data[PI]["EON"][PI_ON]["PSN"]) - name_list[variant*2:variant*2+2]=segment - if (name_list[variant*2:variant*2+2]==list(segment)):#segment already there - segmentcolor="purple" - elif(name_list[variant*2:variant*2+2]==['_']*2): #segment new - segmentcolor="purple" - name_list[variant*2:variant*2+2]=segment - else:#name changed (böse) - segmentcolor="red" - name_list=['_']*8 #reset name - name_list[variant*2:variant*2+2]=segment - #reset stored text: - self.RDS_data[PI_ON]["PSN"]="_"*8 - self.RDS_data[PI_ON]["PSN_valid"]=[False]*8 - self.RDS_data[PI_ON]["PSN_valid"][variant*2:variant*2+2]=[True] *2 - PS_ON_str="".join(name_list) - self.RDS_data[PI_ON]["PSN"]=PS_ON_str - self.RDS_data[PI]["EON"][PI_ON]["PSN"]=PS_ON_str - #determine if text is valid - valid=True - for i in range(0,8): - if (not self.RDS_data[PI_ON]["PSN_valid"][i]): - valid = False - if(valid): - #textcolor="black" - textcolor=""#use default color (white if background is black) - - else: - textcolor="gray" - formatted_text=self.color_text(self.RDS_data[PI_ON]["PSN"],variant*2,variant*2+2,textcolor,segmentcolor) - self.RDS_data[PI]["EON"][PI_ON]["PSN"]=PS_ON_str - self.RDS_data[PI_ON]["PSN"]=PS_ON_str - #formatted_text="%s"%("purple",PS_ON_str) - self.signals.DataUpdateEvent.emit({'PI':PI_ON,'PSN':formatted_text}) - try: - t=(PI_ON,self.RDS_data[PI_ON]["PSN"],float(self.RDS_data[PI_ON]["AF"]["main"]),self.RDS_data[PI_ON]["PTY"],int(self.RDS_data[PI_ON]["TP"])) - if self.writeDB: - db.execute("INSERT OR REPLACE INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t) - except KeyError: - #not all info present -> no db update - pass - if variant==4:#AF_ON - if self.debug: - print("AF_ON method A")#TODO - if variant in range(5,10):#variant 5..9 -> mapped freqs - freq_TN=self.decode_AF_freq(array[4]) - freq_ON=self.decode_AF_freq(array[5]) - #lock in tuned network if freq_TN matches decoder frequency - if(self.RDS_data[PI].has_key("tuned_freq") and freq_TN==self.RDS_data[PI]["tuned_freq"]and not self.RDS_data[PI]["AF"].has_key("main")): - if self.log: - print("main frequency found in 14A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq_TN/1e6)) - self.RDS_data[PI]["AF"]["main"]=freq_TN - freq_str="EON_TN:%0.1fM"% (freq_TN/1e6) - self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) - #lock in ON if TN is locked in - if(self.RDS_data[PI]["AF"].has_key("main") and self.RDS_data[PI]["AF"]["main"]==freq_TN and not self.RDS_data[PI_ON]["AF"].has_key("main")): - if self.log: - print("mapped frequency found in 14A: station:%s, freq:%0.1fM"% (self.RDS_data[PI_ON]["PSN"],freq_ON/1e6)) - self.RDS_data[PI_ON]["AF"]["main"]=freq_ON - freq_str="EON:%0.1fM"% (freq_ON/1e6) - self.signals.DataUpdateEvent.emit({'PI':PI_ON,'freq':freq_str}) - #print("mapped freq in variant %i:, %i->%i"%(variant,freq_TN,freq_ON)) - if variant==13:#PTY and TA of ON - PTY_ON=array[4]>>3 - TA_ON=array[5]&0x1 - self.RDS_data[PI]["EON"][PI_ON]["TA_ON"]=TA_ON - self.RDS_data[PI]["EON"][PI_ON]["PTY_ON"]=PTY_ON - self.RDS_data[PI_ON]["TA"]=TA_ON - self.RDS_data[PI_ON]["PTY"]=self.pty_dict[PTY_ON] - self.signals.DataUpdateEvent.emit({'PI':PI_ON,'PTY':self.pty_dict[PTY_ON],'TA':TA_ON}) - #rest is reserved - if variant==14:#programme item number of ON - PIN_ON=(array[4]<<8)|(array[5]) - elif (groupType == "8A"): - if self.debug: - print("8A without 3A on PI:%s"%PI) - #else:#other group - #print("group of type %s not decoded on station %s"% (groupType,PI)) - if 1==1: - #printdelay=50 - printdelay=500 - self.printcounter+=0#printing disabled - if self.printcounter == printdelay and self.debug: - - for key in self.RDS_data: - if self.RDS_data[key].has_key("PSN"): - psn=self.RDS_data[key]["PSN"] - else: - psn="?" - print("%s(%s):"%(psn,key),end="") - pp.pprint(self.RDS_data[key]["blockcounts"]) - if self.RDS_data[key].has_key("RT+"): - print("RT+:",end="") - pp.pprint(self.RDS_data[key]["RT+"]) - self.printcounter=0 - - - pr.disable() #disabled-internal-profiling - #end of handle_msg + if pmt.to_long(pmt.car(msg))==1L: + data=pmt.to_python(pmt.cdr(msg)) + #print("port:%i, data: %s"%(port,data)) + self.decoders[port]['synced']=data + self.update_freq() + else: #elif pmt.to_long(pmt.car(msg))==0L + array=pmt.to_python(msg)[1] + + if time.time()-self.save_data_timer > 10:#every 10 seconds + self.save_data_timer=time.time() + self.clean_data_and_commit_db() + + if time.time()-self.minute_count_timer > 3:#every 3 second + self.minute_count_max=self.minute_count + self.signals.DataUpdateEvent.emit({'group_count':self.minute_count,'group_count_max':self.minute_count_max}) + self.minute_count=0 + self.minute_count_timer=time.time() + #pr.enable()#disabled-internal-profiling + self.minute_count+=1 + #self.signals.DataUpdateEvent.emit({'group_count':self.minute_count,'group_count_max':self.minute_count_max}) + if self.writeDB: + #db=sqlite3.connect(self.db_name) + db=self.db + + + groupNR=array[2]&0b11110000 + groupVar=array[2]&0b00001000 + if (groupVar == 0): + groupType=str(groupNR >> 4)+"A" + else: + groupType=str(groupNR >> 4)+"B" + #if self.debug: + #PI=str(port)+"_%02X%02X" %(array[0],array[1]) + #else: + #PI="%02X%02X" %(array[0],array[1]) + PI="%02X%02X" %(array[0],array[1]) + TP=(array[2]>>2)&0x1 + block2=(array[2]<<8)|(array[3]) #block2 + PTY=(block2>>5)&0x1F + wrong_blocks=int(array[12]) + + try: + self.PI_dict[PI]+=1 + except KeyError: + pass + + if not self.RDS_data.has_key(PI):#station invalid/new + if not self.PI_dict.has_key(PI):#1st group + self.PI_dict[PI]=1 + return#dont decode further if not yet valid + elif self.PI_dict[PI]>2:#count station as valid if more than 2 packets received + self.init_data_for_PI(PI)#initialize dict for station + if self.log: + print("found station %s"%PI) + else: + return#dont decode further if not yet valid + + if self.decoder_frequencies.has_key(port): + freq=self.decoder_frequencies[port] + freq_str="%i:%0.1fM"% (port,freq/1e6) + self.RDS_data[PI]["tuned_freq"]=freq + #self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) + if self.RDS_data[PI]["blockcounts"].has_key(groupType): + self.RDS_data[PI]["blockcounts"][groupType] +=1 #increment + else: + self.RDS_data[PI]["blockcounts"][groupType] = 1 #initialize (1st group of this type) + self.RDS_data[PI]["blockcounts"]["any"]+=1 + if self.RDS_data[PI]["blockcounts"]["any"]==5: + self.RDS_data[PI]["blockcounts"]["any"]=0 + if self.writeDB: + t=(str(PI),groupType,self.RDS_data[PI]["blockcounts"][groupType])#TODO only update DB every few seconds + db.execute("INSERT OR REPLACE INTO grouptypeCounts (PI,grouptype,count) VALUES (?,?,?)",t) + dots="."*self.RDS_data[PI]["blockcounts"]["any"] + self.RDS_data[PI]["TP"]=TP + self.RDS_data[PI]["PTY"]=self.pty_dict[PTY] + + self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'PTY':self.pty_dict[PTY],'TP':TP,'wrong_blocks':wrong_blocks,'dots':dots}) + + + + #add any received groups to DB (slow) + #content="%02X%02X%02X%02X%02X" %(array[3]&0x1f,array[4],array[5],array[6],array[7]) + #t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],groupType,content) + #db.execute("INSERT INTO groups VALUES (?,?,?,?,?)",t) + + if (groupType == "0A"):#AF PSN + adr=array[3]&0b00000011 + segment=self.decode_chars(chr(array[6])+chr(array[7])) + d=(array[3]>>2)&0x1 + self.RDS_data[PI]["DI"][3-adr]=d#decoder information + #DI[0]=d0 0=Mono 1=Stereo + #d1 Not artificial head Artificial head + #d2 Not compressed Compressed + #d3 Static PTY Dynamic PTY + TA=(array[3]>>4)&0x1 + MS=(array[3]>>3)&0x1 + self.RDS_data[PI]["TA"]=TA + #style='font-family:Courier New;color:%s' + flag_string="TP:%i, TA:%i, MS:%i, DI:%s"%(TP,TA,MS,str(self.RDS_data[PI]["DI"])) + pty_colored=self.RDS_data[PI]["PTY"] + if TP==1: + if TA==1: + color="red" + elif TA==0: + color="green" + else: + color="yellow" + pty_colored="%s"%(color,self.RDS_data[PI]["PTY"]) + + self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'flags':flag_string,'PTY':pty_colored}) + + #224 1110 0000 = no AF + #225 1110 0001 = 1AF + #249 1111 1001 = 25AF + fillercode=205#1100 1101 + if not self.RDS_data[PI]["AF"].has_key("main") and self.RDS_data[PI].has_key("tuned_freq"): + #if self.RDS_data[PI].has_key("tuned_freq"):#update main freq even if one exists -> DB problem + freq=self.decode_AF_freq(array[4]) + if freq==self.RDS_data[PI]["tuned_freq"]: + self.RDS_data[PI]["AF"]["main"]=freq + if self.log: + print("main frequency found in 0A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq/1e6)) + freq_str="0A:%0.1fM"% (freq/1e6) + self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) + t=(PI,self.RDS_data[PI]["PSN"],float(freq),self.RDS_data[PI]["PTY"],int(self.RDS_data[PI]["TP"])) + if self.writeDB: + db.execute("INSERT INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t) + freq=self.decode_AF_freq(array[5]) + if freq==self.RDS_data[PI]["tuned_freq"]: + self.RDS_data[PI]["AF"]["main"]=freq + if self.log: + print("main frequency found in 0A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq/1e6)) + freq_str="0A:%0.1fM"% (freq/1e6) + self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) + t=(PI,self.RDS_data[PI]["PSN"],float(freq),self.RDS_data[PI]["PTY"],int(self.RDS_data[PI]["TP"])) + if self.writeDB: + db.execute("INSERT INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t) + if self.RDS_data[PI].has_key("tuned_freq") :#TODO add secondary freqs + freq=self.decode_AF_freq(array[4]) + diff=abs(freq-self.RDS_data[PI]["tuned_freq"]) + if diff<100000: + self.RDS_data[PI]["AF"]["rxset"].add(freq) + freq=self.decode_AF_freq(array[5]) + diff=abs(freq-self.RDS_data[PI]["tuned_freq"]) + if diff<100000: + self.RDS_data[PI]["AF"]["rxset"].add(freq) + + if(array[4]>= 224 and array[4]<= 249): + #print("AF1 detected") + self.RDS_data[PI]["AF"]['number']=array[4]-224 + #self.RDS_data[PI]["AF"]['main']=self.decode_AF_freq(array[5]) + self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'AF':self.RDS_data[PI]["AF"]}) + if(array[5]>= 224 and array[5]<= 249): + print("AF2 detected (shouldn't happen) %s"%array[5]) + + + #add frequencies to set + self.RDS_data[PI]["AF"]["set"].add(self.decode_AF_freq(array[4])) + self.RDS_data[PI]["AF"]["set"].add(self.decode_AF_freq(array[5])) + try: + self.RDS_data[PI]["AF"]["set"].remove(0)#remove control characters + except KeyError: + pass + + name_list=list(self.RDS_data[PI]["PSN"]) + if (name_list[adr*2:adr*2+2]==list(segment)):#segment already there + segmentcolor="green" + elif(name_list[adr*2:adr*2+2]==['_']*2): #segment new + segmentcolor="orange" + name_list[adr*2:adr*2+2]=segment + else:#name changed (böse) + segmentcolor="red" + name_list=['_']*8 #reset name + name_list[adr*2:adr*2+2]=segment + #reset stored text: + self.RDS_data[PI]["PSN"]="_"*8 + self.RDS_data[PI]["PSN_valid"]=[False]*8 + self.RDS_data[PI]["PSN_valid"][adr*2:adr*2+2]=[True] *2 + self.RDS_data[PI]["PSN"]="".join(name_list) + #determine if text is valid + valid=True + for i in range(0,8): + if (not self.RDS_data[PI]["PSN_valid"][i]): + valid = False + if(valid): + #textcolor="black" + textcolor=""#use default color (white if background is black) + if not self.RDS_data[PI]["internals"]["last_valid_psn"]==self.RDS_data[PI]["PSN"]:#ignore duplicates + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"PSN_valid",self.RDS_data[PI]["PSN"]) + if self.writeDB: + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + t=(self.RDS_data[PI]["PSN"],PI) + if self.writeDB: + db.execute("UPDATE OR IGNORE stations SET PSN=? WHERE PI IS ?",t) + self.RDS_data[PI]["internals"]["last_valid_psn"]=self.RDS_data[PI]["PSN"] + else: + textcolor="gray" + formatted_text=self.color_text(self.RDS_data[PI]["PSN"],adr*2,adr*2+2,textcolor,segmentcolor) + self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'PSN':formatted_text}) + elif (groupType == "1A"):#PIN programme item number + #wer nutzt 1A gruppen? + #antenne1: variants: 0(ECC), + PIN=(array[6]<<8)|(array[7]) + SLC=(array[4]<<8)|(array[5])&0xfff#slow labeling code + radio_paging=array[3]&0x1f + LA=array[4]>>7#linkage actuator + variant=(array[4]>>4)&0x7 + PIN_day=(PIN>>11)&0x1f + PIN_hour=(PIN>>6)&0x1f + PIN_minute=PIN&0x3f + PIN_valid= PIN_day in range(1,32) and PIN_hour in range(0,24) and PIN_minute in range(0,60) + if PIN_valid: + self.RDS_data[PI]["PIN"]=[PIN_day,PIN_hour,PIN_minute] + data_string="variant:%i,SLC:%04X,PIN (valid):%s "%(variant,SLC,str([PIN_day,PIN_hour,PIN_minute])) + else: + data_string="variant:%i,SLC:%04X,PIN:%04X "%(variant,SLC,PIN) + #%02X%02X%02X%02X%02X + + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"PIN",data_string) + if self.writeDB: + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + if self.debug and not variant==0:#print if not seen before + print("PI:%s PSN:%s uses variant %i of 1A"%(PI,self.RDS_data[PI]["PSN"],variant)) + if variant==0: + paging=array[4]&0xf + extended_country_code=array[5] + self.RDS_data[PI]["ECC"]=extended_country_code + if self.debug: + print("1A variant 0: PI:%s PSN:%s,ECC:%s"%(PI,self.RDS_data[PI]["PSN"],hex(extended_country_code))) + elif variant==1: + TMC_info=SLC + elif variant==2: + paging_info=SLC + elif variant==3: + language_codes=SLC + if self.debug: + print("PI:%s PSN:%s,language_codes:%s"%(PI,self.RDS_data[PI]["PSN"],hex(language_codes))) + elif variant==6: + #for use by broadcasters + if self.debug: + print("PI:%s PSN:%s uses variant 6 of 1A"%(PI,self.RDS_data[PI]["PSN"])) + elif variant==7: + ESW_channel_identification=SLC + #end of 1A decode + elif (groupType == "2A"):#RT radiotext + if(not self.RDS_data[PI].has_key("RT_0")):#initialize variables + self.RDS_data[PI]["RT_0"]={"RT":"_"*64,"RT_valid":[False]*64,"RT_all_valid":False} + self.RDS_data[PI]["RT_1"]={"RT":"_"*64,"RT_valid":[False]*64,"RT_all_valid":False} + #self.RDS_data[PI]["RT"]="_"*64 + #self.RDS_data[PI]["RT_valid"]=[False]*64 + #self.RDS_data[PI]["RT_all_valid"]=False + self.RDS_data[PI]["RT_last_ab_flag"]=2 + + adr= array[3]&0b00001111 + ab_flag=(array[3]&0b00010000)>>4 + + self.RDS_data[PI]["RT_last_ab_flag"] =ab_flag + + #segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7])) + segment=chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7])#EDIT:latedecode + + #print("RT:adress: %d, segment:%s"%(adr,segment)) + #self.signals.DataUpdateEvent.emit({'col':5,'row':port,'PI':PI,'groupType':groupType,'adress':adr,'segment':segment}) + text_list=list(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]) + #determine text length: + try: + text_end=text_list.index('\r') + except ValueError: + text_end=64 #assume whole string is important + pass + predicted=False + if (text_list[adr*4:adr*4+4]==list(segment)):#segment already there + segmentcolor="green" + elif (text_list[adr*4:adr*4+4]==['_']*4):#segment new + segmentcolor="orange" + text_list[adr*4:adr*4+4]=segment + else: + segmentcolor="red" + text_list=['_']*64 #clear text + text_list[adr*4:adr*4+4]=segment + #reset stored text: + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="_"*64 + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"]=[False]*64 + #predict RT from last texts: + for rt in self.RDS_data[PI]["internals"]["RT_history"]: + if rt[adr*4:adr*4+4]==list(segment): + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="".join(rt) + predicted=True + + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"][adr*4:adr*4+4]=[True] *4 + if not predicted: + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="".join(text_list) + + #determine if (new) text is valid + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"]=True + for i in range(0,text_end): + if (not self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"][i]): + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"] = False + if(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"]): + #textcolor="black" + textcolor=""#use default color (white if background is black) + l=list(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]) + rt="".join(l[0:text_end])#remove underscores(default symbol) after line end marker + if not self.RDS_data[PI]["internals"]["last_valid_rt"]==rt:#ignore duplicates #TODO add 2nd order duplicates ABAB + self.RDS_data[PI]["internals"]["RT_history"].append(l) + if len(self.RDS_data[PI]["internals"]["RT_history"])>10:#only store last 10 RTs + self.RDS_data[PI]["internals"]["RT_history"].pop(0) + if self.writeDB: + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT",self.decode_chars(rt)) + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + self.RDS_data[PI]["internals"]["last_valid_rt"]=rt + try:#save rt+ if it exist + if self.writeDB: + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT+",self.decode_chars(str(self.RDS_data[PI]["RT+"]))) + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + except KeyError: + pass#no rt+ -> dont save + else: + textcolor="gray" + display_text=self.decode_chars(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"].split("\r")[0]) + formatted_text=self.color_text(display_text,adr*4,adr*4+4,textcolor,segmentcolor) + rtcol=self.colorder.index('text') + self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'string':formatted_text}) + + elif (groupType == "3A"):#ODA announcements (contain application ID "AID") + AID=(array[6]<<8)|(array[7])#combine 2 bytes into 1 block + app_data=(array[4]<<8)|(array[5])#content defined by ODA-app + app_group_raw=array[3]&0x1f #group type in which this app is sent + if (app_group_raw&0x1 == 0): + app_group=str(app_group_raw >> 1)+"A" + else: + app_group=str(app_group_raw >> 1)+"B" + + if not self.RDS_data[PI]["AID_list"].has_key(AID):#new ODA found + try: + app_name=self.ODA_application_names[AID] + except KeyError: + if self.debug: + print("ERROR: ODA-app-id (AID) '%i' not found in list on station %s, app group:%s"%(AID,app_group,PI)) + app_name="unknown" + self.RDS_data[PI]["AID_list"][AID]={} + self.RDS_data[PI]["AID_list"][AID]["groupType"]=app_group + self.RDS_data[PI]["AID_list"][AID]["app_name"]=app_name + self.RDS_data[PI]["AID_list"][AID]["app_data"]=app_data + if AID==52550:#TMC alert-c initialize + self.RDS_data[PI]["AID_list"][AID]["provider name"]="________" + if self.log: + print("new ODA: AID:%i, name:'%s', app_group:%s, station:%s" %(AID,app_name,app_group,PI)) + #decode 3A group of TMC + if AID==52550:#TMC alert-c (continuously update) + variant=app_data>>14 + if variant==0: + self.RDS_data[PI]["AID_list"][AID]["LTN"]=(app_data>>6)&0x3f#location table number (6 bits) + self.RDS_data[PI]["AID_list"][AID]["AFI"]=(app_data>>5)&0x1#alternative frequency indicator + self.RDS_data[PI]["AID_list"][AID]["M"]=(app_data>>4)&0x1#transmission mode indicator + #Message Geographical Scope: + self.RDS_data[PI]["AID_list"][AID]["scope"]="" + if (app_data>>3)&0x1==1: + self.RDS_data[PI]["AID_list"][AID]["scope"]+="I"#international (EUROROAD) + if (app_data>>2)&0x1==1: + self.RDS_data[PI]["AID_list"][AID]["scope"]+="N"#national + if (app_data>>1)&0x1==1: + self.RDS_data[PI]["AID_list"][AID]["scope"]+="R"#regional + if (app_data>>0)&0x1==1: + self.RDS_data[PI]["AID_list"][AID]["scope"]+="U"#urban + #self.RDS_data[PI]["AID_list"][AID]["I"]=(app_data>>3)&0x1#international (EUROROAD) + #self.RDS_data[PI]["AID_list"][AID]["N"]=(app_data>>2)&0x1#national + #self.RDS_data[PI]["AID_list"][AID]["R"]=(app_data>>1)&0x1#regional + #self.RDS_data[PI]["AID_list"][AID]["U"]=(app_data>>0)&0x1#urban + elif variant==1: + self.RDS_data[PI]["AID_list"][AID]["SID"]=(app_data>>6)&0x3f#service identifier + #timing parameters (used to switch away from TMC station without missing messages): + self.RDS_data[PI]["AID_list"][AID]["G"]=(app_data>>12)&0x3#gap parameter + self.RDS_data[PI]["AID_list"][AID]["activity_time"]=(app_data>>4)&0x3 + self.RDS_data[PI]["AID_list"][AID]["window_time"]=(app_data>>2)&0x3 + self.RDS_data[PI]["AID_list"][AID]["delay_time"]=(app_data>>0)&0x3 + elif self.debug: + print("unknown variant %i in TMC 3A group"%variant) + send_pmt = pmt.pmt_to_python.pmt_from_dict({ + "type":"3A_meta", + "data":self.RDS_data[PI]["AID_list"][AID]}) + self.message_port_pub(pmt.intern('tmc_raw'), send_pmt) + elif (groupType == "4A"):#CT clock time + bits=BitArray('uint:8=%i,uint:8=%i,uint:8=%i,uint:8=%i,uint:8=%i'%tuple(array[3:8])) + spare,datecode,hours,minutes,offsetdir,local_time_offset = bits.unpack("uint:6,uint:17,uint:5,uint:6,uint:1,uint:5") + local_time_offset*=0.5 + #datecode=((array[3] & 0x03) << 15) | (array[4] <<7)|((array[5] >> 1) & 0x7f)#modified julian date + if datecode==0: + #do not update!! + if self.debug: + print("station:%s sent empty 4A group"%self.RDS_data[PI]["PSN"]) + else: + #hours=((array[5] & 0x1) << 4) | ((array[6] >> 4) & 0x0f) + #minutes=((array[6] &0x0F)<<2)|((array[7] >>6)&0x3) + #offsetdir=(array[7]>>5)&0x1 + #local_time_offset=0.5*((array[7])&0x1F) + if(offsetdir==1): + local_time_offset*=-1 + + date=datetime(1858,11,17)+timedelta(days=int(datecode))#convert from MJD (modified julian date) + + timestring="%02i:%02i (%+.1fh)" % (hours,minutes,local_time_offset) + datestring=date.strftime("%d.%m.%Y") + ctcol=self.colorder.index('time') + self.signals.DataUpdateEvent.emit({'col':ctcol,'row':port,'PI':PI,'string':timestring,'tooltip':datestring}) + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"CT",datestring+" "+timestring+"; datecode(MJD):"+str(datecode)) + self.RDS_data[PI]["time"]["timestring"]=timestring + self.RDS_data[PI]["time"]["datestring"]=datestring + try: + self.RDS_data[PI]["time"]["datetime"]=datetime(date.year,date.month,date.day,hours,minutes)+timedelta(hours=local_time_offset) + except ValueError: + print("ERROR: could not interpret time or date:"+datestring+" "+timestring) + if self.writeDB: + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + elif (groupType == "6A"):#IH inhouse data -> save for analysis + """In House Data: + {'130A': {'1E1077FFFF': {'count': 1, + 'last_time': '2016-12-08 16:26:54.767596'}, + '1F23022015': {'count': 1, + 'last_time': '2016-12-08 16:26:56.341271'}}, + 'D00F': {'1E1023FFFF': {'count': 1, + 'last_time': '2016-12-08 16:26:54.769165'}, + '1F28032008': {'count': 3, + 'last_time': '2016-12-08 16:26:58.272420'}}}""" + ih_data="%02X%02X%02X%02X%02X" %(array[3]&0x1f,array[4],array[5],array[6],array[7]) + if not self.IH_data.has_key(PI): + self.IH_data[PI]={} + if not self.IH_data[PI].has_key(ih_data): + self.IH_data[PI][ih_data]={} + self.IH_data[PI][ih_data]["count"]=0 + self.IH_data[PI][ih_data]["count"]+=1 + self.IH_data[PI][ih_data]["last_time"]=str(datetime.now()) + #TMC-alert-c (grouptype mostly 8A): + elif self.RDS_data[PI]["AID_list"].has_key(52550) and self.RDS_data[PI]["AID_list"][52550]["groupType"]==groupType:#TMC alert-C + tmc_x=array[3]&0x1f #lower 5 bit of block2 + tmc_y=(array[4]<<8)|(array[5]) #block3 + tmc_z=(array[6]<<8)|(array[7])#block4 + send_pmt = pmt.pmt_to_python.pmt_from_dict({ + "type":"alert-c", + "PI":PI, + "PSN":self.RDS_data[PI]["PSN"], + "TMC_X":tmc_x, + "TMC_Y":tmc_y, + "TMC_Z":tmc_z + }) + self.message_port_pub(pmt.intern('tmc_raw'), send_pmt) + tmc_hash=md5.new(str([PI,tmc_x,tmc_y,tmc_z])).hexdigest() + tmc_T=tmc_x>>4 #0:TMC-message 1:tuning info/service provider name + tmc_F=int((tmc_x>>3)&0x1) #identifies the message as a Single Group (F = 1) or Multi Group (F = 0) + Y15=int(tmc_y>>15) + datetime_received=self.RDS_data[PI]["time"]["datetime"] + if tmc_T == 0: + if tmc_F==1:#single group + tmc_msg=tmc_message(PI,tmc_x,tmc_y,tmc_z,datetime_received,self) + self.print_tmc_msg(tmc_msg) + elif tmc_F==0 and Y15==1:#1st group of multigroup + ci=int(tmc_x&0x7) + tmc_msg=tmc_message(PI,tmc_x,tmc_y,tmc_z,datetime_received,self) + #if self.RDS_data[PI]["internals"]["unfinished_TMC"].has_key(ci): + #print("overwriting parital message") + self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]={"msg":tmc_msg,"time":time.time()} + else: + ci=int(tmc_x&0x7) + if self.RDS_data[PI]["internals"]["unfinished_TMC"].has_key(ci): + tmc_msg=self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["msg"] + tmc_msg.add_group(tmc_y,tmc_z) + age=time.time()-self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["time"] + t=(time.time(),PI,age,ci,tmc_msg.is_complete) + #print("%f: continuing message PI:%s,age:%f,ci:%i complete:%i"%t) + self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["time"]=time.time() + if tmc_msg.is_complete: + self.print_tmc_msg(tmc_msg)#print and store message + del self.RDS_data[PI]["internals"]["unfinished_TMC"][tmc_msg.ci]#delete finished message + else: + #if not ci==0: + #print("ci %i not found, discarding"%ci) + pass + + else:#alert plus or provider info + adr=tmc_x&0xf + if 4 <= adr and adr <= 9: + #seen variants 4569, 6 most often + #print("TMC-info variant:%i"%adr) + if adr==4 or adr==5:#service provider name + segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7])) + if self.debug: + print("TMC-info adr:%i (provider name), segment:%s, station:%s"%(adr,segment,self.RDS_data[PI]["PSN"])) + if self.RDS_data[PI]["AID_list"].has_key(52550): + text_list=list(self.RDS_data[PI]["AID_list"][52550]["provider name"]) + seg_adr_start=(adr-4)*4#start of segment + text_list[seg_adr_start:seg_adr_start+4]=segment + self.RDS_data[PI]["AID_list"][52550]["provider name"]="".join(text_list) + if adr== 7:#freq of tuned an mapped station (not seen yet) + freq_TN=tmc_y>>8 + freq_ON=tmc_y&0xff#mapped frequency + if self.debug: + print("TMC-info: TN:%i, station:%s"%(freq_TN,self.RDS_data[PI]["PSN"])) + self.RDS_data[PI]["TMC_TN"]=freq_TN + else: + if self.debug: + print("alert plus on station %s (%s)"%(PI,self.RDS_data[PI]["PSN"]))#(not seen yet) + + #self.tableobj.RDS_data["D301"]["AID_list"][52550]["provider name"]="test____" + #RadioText+ (grouptype mostly 12A): + elif self.RDS_data[PI]["AID_list"].has_key(19415) and self.RDS_data[PI]["AID_list"][19415]["groupType"]==groupType:#RT+ + if not self.RDS_data[PI].has_key("RT+"): + #self.RDS_data[PI]["RT+"]={"history":{},"last_item_toggle_bit":2} + self.RDS_data[PI]["RT+"]={"last_item_toggle_bit":2} + self.RDS_data[PI]["RT+_history"]={} + self.RDS_data[PI]["internals"]["RT+_times"]={} + #self.RDS_data[PI]["RT+"]["last_item_toggle_bit"]=2 + A3_data=self.RDS_data[PI]["AID_list"][19415]["app_data"] + template_number=A3_data&0xff + SCB=(A3_data >> 8)&0x0f#server control bit + CB_flag=(A3_data>>12)&0x1 #is set if template available + rtp_message= ((array[3]&0x1f)<<32)|(array[4]<<24)|(array[5]<<16)|(array[6]<<8)|(array[7]) + item_toggle_bit=(rtp_message>>36)&0x1 + item_running_bit=(rtp_message>>35)&0x1 + tag1=(rtp_message>>17)&(2**18-1)#6+6+6 + tag2=(rtp_message)&(2**17-1)#6+6+5 + tag1_type=self.rtp_classnames[int(tag1>>12)] + tag2_type=self.rtp_classnames[int(tag2>>11)] + tag1_start=int((tag1>>6)&(2**6-1)) + tag1_len=int(tag1&(2**6-1)) + tag2_start=int((tag2>>5)&(2**6-1)) + tag2_len=int(tag2&(2**5-1)) + if not self.RDS_data[PI]["RT+"]["last_item_toggle_bit"] == item_toggle_bit: #new item + #self.RDS_data[PI]["RT+"]["history"][str(datetime.now())]=self.RDS_data[PI]["internals"]["last_rt_tooltip"] + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT+",str(self.RDS_data[PI]["RT+"])) + if self.writeDB: + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + self.RDS_data[PI]["RT+_history"][str(datetime.now())]=copy.deepcopy(self.RDS_data[PI]["RT+"])#save old item + self.RDS_data[PI]["RT+"]["last_item_toggle_bit"] = item_toggle_bit + rtcol=self.colorder.index('text') + if self.debug: + print("toggle bit changed on PI:%s, cleared RT-tt"%PI) + self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'tooltip':""}) + if self.RDS_data[PI].has_key("RT_0"): + ab_flag=self.RDS_data[PI]["RT_last_ab_flag"] + rt=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"] + rt_valid=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"] + if not tag1_type=="DUMMY_CLASS" and all(rt_valid[tag1_start:tag1_start+tag1_len+1]): + self.RDS_data[PI]["RT+"][tag1_type]=rt[tag1_start:tag1_start+tag1_len+1] + self.RDS_data[PI]["internals"]["RT+_times"][tag1_type]=time.time() + if not tag2_type=="DUMMY_CLASS" and all(rt_valid[tag2_start:tag2_start+tag2_len+1]): + self.RDS_data[PI]["RT+"][tag2_type]=rt[tag2_start:tag2_start+tag2_len+1] + self.RDS_data[PI]["internals"]["RT+_times"][tag2_type]=time.time() + #check outdated tags: + for tagtype in self.RDS_data[PI]["internals"]["RT+_times"].keys():#.keys() makes copy to avoid RuntimeError: dictionary changed size during iteration + age=time.time()-self.RDS_data[PI]["internals"]["RT+_times"][tagtype] + if age>90:#delete if older than 90 sek#TODO delete if toggle bit changes?, delete if tag changes? (title change -> delete artist) + del self.RDS_data[PI]["internals"]["RT+_times"][tagtype] + del self.RDS_data[PI]["RT+"][tagtype] + + + tags="ir:%i,it:%i"%(item_running_bit,item_toggle_bit) + rtpcol=self.colorder.index('RT+') + self.signals.DataUpdateEvent.emit({'col':rtpcol,'row':port,'PI':PI,'string':tags}) + if(tag2_type=="ITEM.TITLE" and self.RDS_data[PI].has_key("RT_0")):#TODO remove duplicate code + ab_flag=self.RDS_data[PI]["RT_last_ab_flag"] + rt=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"] + rt_valid=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"] + artist="?" + song="?" + if all(rt_valid[tag1_start:tag1_start+tag1_len+1]): + artist=rt[tag1_start:tag1_start+tag1_len+1] + if all(rt_valid[tag2_start:tag2_start+tag2_len+1]): + song=rt[tag2_start:tag2_start+tag2_len+1] + formatted_text="%s by %s"%(song,artist) + rtcol=self.colorder.index('text') + #only update tooltip if text changed -> remove flicker, still flickers :( + if not formatted_text == self.RDS_data[PI]["internals"]["last_rt_tooltip"]: + self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'tooltip':formatted_text}) + self.RDS_data[PI]["internals"]["last_rt_tooltip"] = formatted_text + #elif(not tag1_type=="ITEM.ARTIST" and not tag1_type=="DUMMY_CLASS"): + # print("%s:RT+: tag1_type:%s, tag2_type:%s"%(PI,tag1_type,tag2_type)) + elif (groupType == "14A"):#EON enhanced other networks + #TN = tuned network, ON=other network + if not self.RDS_data[PI].has_key("EON"): + self.RDS_data[PI]["EON"]={} + TP_ON=(array[3]>>4)&0x1 + PI_ON="%02X%02X" %(array[6],array[7]) + variant=array[3]&0xf + self.signals.DataUpdateEvent.emit({'PI':PI_ON,'TP':TP_ON}) + if not self.RDS_data.has_key(PI_ON): + self.init_data_for_PI(PI_ON) + self.RDS_data[PI_ON]["TP"]=TP_ON + self.PI_dict[PI_ON]=0#initialize dict, even if no packets received + if self.log: + print("found station %s via EON on station %s"%(PI_ON,PI)) + if not self.RDS_data[PI]["EON"].has_key(PI_ON): + self.RDS_data[PI]["EON"][PI_ON]={} + self.RDS_data[PI]["EON"][PI_ON]["PSN"]="_"*8 + if variant in range(4):#variant 0..3 -> PS_ON + segment=self.decode_chars(chr(array[4])+chr(array[5])) + name_list=list(self.RDS_data[PI_ON]["PSN"]) + #name_list=list(self.RDS_data[PI]["EON"][PI_ON]["PSN"]) + name_list[variant*2:variant*2+2]=segment + if (name_list[variant*2:variant*2+2]==list(segment)):#segment already there + segmentcolor="purple" + elif(name_list[variant*2:variant*2+2]==['_']*2): #segment new + segmentcolor="purple" + name_list[variant*2:variant*2+2]=segment + else:#name changed (böse) + segmentcolor="red" + name_list=['_']*8 #reset name + name_list[variant*2:variant*2+2]=segment + #reset stored text: + self.RDS_data[PI_ON]["PSN"]="_"*8 + self.RDS_data[PI_ON]["PSN_valid"]=[False]*8 + self.RDS_data[PI_ON]["PSN_valid"][variant*2:variant*2+2]=[True] *2 + PS_ON_str="".join(name_list) + self.RDS_data[PI_ON]["PSN"]=PS_ON_str + self.RDS_data[PI]["EON"][PI_ON]["PSN"]=PS_ON_str + #determine if text is valid + valid=True + for i in range(0,8): + if (not self.RDS_data[PI_ON]["PSN_valid"][i]): + valid = False + if(valid): + #textcolor="black" + textcolor=""#use default color (white if background is black) + + else: + textcolor="gray" + formatted_text=self.color_text(self.RDS_data[PI_ON]["PSN"],variant*2,variant*2+2,textcolor,segmentcolor) + self.RDS_data[PI]["EON"][PI_ON]["PSN"]=PS_ON_str + self.RDS_data[PI_ON]["PSN"]=PS_ON_str + #formatted_text="%s"%("purple",PS_ON_str) + self.signals.DataUpdateEvent.emit({'PI':PI_ON,'PSN':formatted_text}) + try: + t=(PI_ON,self.RDS_data[PI_ON]["PSN"],float(self.RDS_data[PI_ON]["AF"]["main"]),self.RDS_data[PI_ON]["PTY"],int(self.RDS_data[PI_ON]["TP"])) + if self.writeDB: + db.execute("INSERT OR REPLACE INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t) + except KeyError: + #not all info present -> no db update + pass + if variant==4:#AF_ON + if self.debug: + print("AF_ON method A")#TODO + if variant in range(5,10):#variant 5..9 -> mapped freqs + freq_TN=self.decode_AF_freq(array[4]) + freq_ON=self.decode_AF_freq(array[5]) + #lock in tuned network if freq_TN matches decoder frequency + if(self.RDS_data[PI].has_key("tuned_freq") and freq_TN==self.RDS_data[PI]["tuned_freq"]and not self.RDS_data[PI]["AF"].has_key("main")): + if self.log: + print("main frequency found in 14A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq_TN/1e6)) + self.RDS_data[PI]["AF"]["main"]=freq_TN + freq_str="EON_TN:%0.1fM"% (freq_TN/1e6) + self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) + #lock in ON if TN is locked in + if(self.RDS_data[PI]["AF"].has_key("main") and self.RDS_data[PI]["AF"]["main"]==freq_TN and not self.RDS_data[PI_ON]["AF"].has_key("main")): + if self.log: + print("mapped frequency found in 14A: station:%s, freq:%0.1fM"% (self.RDS_data[PI_ON]["PSN"],freq_ON/1e6)) + self.RDS_data[PI_ON]["AF"]["main"]=freq_ON + freq_str="EON:%0.1fM"% (freq_ON/1e6) + self.signals.DataUpdateEvent.emit({'PI':PI_ON,'freq':freq_str}) + #print("mapped freq in variant %i:, %i->%i"%(variant,freq_TN,freq_ON)) + if variant==13:#PTY and TA of ON + PTY_ON=array[4]>>3 + TA_ON=array[5]&0x1 + self.RDS_data[PI]["EON"][PI_ON]["TA_ON"]=TA_ON + self.RDS_data[PI]["EON"][PI_ON]["PTY_ON"]=PTY_ON + self.RDS_data[PI_ON]["TA"]=TA_ON + self.RDS_data[PI_ON]["PTY"]=self.pty_dict[PTY_ON] + self.signals.DataUpdateEvent.emit({'PI':PI_ON,'PTY':self.pty_dict[PTY_ON],'TA':TA_ON}) + #rest is reserved + if variant==14:#programme item number of ON + PIN_ON=(array[4]<<8)|(array[5]) + elif (groupType == "8A"): + if self.debug: + print("8A without 3A on PI:%s"%PI) + #else:#other group + #print("group of type %s not decoded on station %s"% (groupType,PI)) + if 1==1: + #printdelay=50 + printdelay=500 + self.printcounter+=0#printing disabled + if self.printcounter == printdelay and self.debug: + + for key in self.RDS_data: + if self.RDS_data[key].has_key("PSN"): + psn=self.RDS_data[key]["PSN"] + else: + psn="?" + print("%s(%s):"%(psn,key),end="") + pp.pprint(self.RDS_data[key]["blockcounts"]) + if self.RDS_data[key].has_key("RT+"): + print("RT+:",end="") + pp.pprint(self.RDS_data[key]["RT+"]) + self.printcounter=0 + + + pr.disable() #disabled-internal-profiling + #end of handle_msg def print_tmc_msg(self,tmc_msg): - try: - PI=tmc_msg.PI - tmc_F=tmc_msg.is_single - tmc_hash=tmc_msg.tmc_hash - refloc_name="" - reflocs=tmc_msg.location.reflocs - if not self.TMC_data.has_key(tmc_hash):#if message new - try: - self.TMC_data[tmc_hash]=tmc_msg - if self.showTMC: - self.signals.DataUpdateEvent.emit({'TMC_log':tmc_msg,'multi_str':tmc_msg.multi_str()}) - #t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"ALERT-C",message_string.decode("utf-8")) - #self.db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) - timestring=self.RDS_data[PI]["time"]["timestring"] - if self.writeDB: - message_string=tmc_msg.db_string() - t=(tmc_hash,timestring,PI, tmc_F,tmc_msg.event.ecn,int(tmc_msg.location.lcn),tmc_msg.tmc_DP,tmc_msg.tmc_D,tmc_msg.tmc_dir,tmc_msg.tmc_extent,message_string.decode("utf-8"),tmc_msg.multi_str().decode("utf-8"),str(tmc_msg.debug_data)) - self.db.execute("INSERT INTO TMC (hash,time,PI, F,event,location,DP,div,dir,extent,text,multi,rawmgm) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",t) - except Exception as e: - print(e) - raise - #print("line 1064") - - except KeyError: - #print("location '%i' not found"%tmc_location) - pass + try: + PI=tmc_msg.PI + tmc_F=tmc_msg.is_single + tmc_hash=tmc_msg.tmc_hash + refloc_name="" + reflocs=tmc_msg.location.reflocs + if not self.TMC_data.has_key(tmc_hash):#if message new + try: + self.TMC_data[tmc_hash]=tmc_msg + if self.showTMC: + self.signals.DataUpdateEvent.emit({'TMC_log':tmc_msg,'multi_str':tmc_msg.multi_str()}) + #t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"ALERT-C",message_string.decode("utf-8")) + #self.db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + timestring=self.RDS_data[PI]["time"]["timestring"] + if self.writeDB: + message_string=tmc_msg.db_string() + t=(tmc_hash,timestring,PI, tmc_F,tmc_msg.event.ecn,int(tmc_msg.location.lcn),tmc_msg.tmc_DP,tmc_msg.tmc_D,tmc_msg.tmc_dir,tmc_msg.tmc_extent,message_string.decode("utf-8"),tmc_msg.multi_str().decode("utf-8"),str(tmc_msg.debug_data)) + self.db.execute("INSERT INTO TMC (hash,time,PI, F,event,location,DP,div,dir,extent,text,multi,rawmgm) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",t) + except Exception as e: + print(e) + raise + #print("line 1064") + + except KeyError: + #print("location '%i' not found"%tmc_location) + pass def print_results(self): - s = StringIO.StringIO() - sortby = 'cumulative' - ps = pstats.Stats(pr, stream=s).sort_stats(sortby) - ps.print_stats() - print(s.getvalue()) + s = StringIO.StringIO() + sortby = 'cumulative' + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + print(s.getvalue()) def decode_AF_freq(self,freq_raw): - #if freq_raw in range(1,205):#1..204 BAD coding -> memory usage - if 1<=freq_raw <=204: - return(87500000+freq_raw*100000)#returns int - #return(87.5e6+freq_raw*0.1e6)#returns float - else: - return(0) + #if freq_raw in range(1,205):#1..204 BAD coding -> memory usage + if 1<=freq_raw <=204: + return(87500000+freq_raw*100000)#returns int + #return(87.5e6+freq_raw*0.1e6)#returns float + else: + return(0) def ref_locs(self,loc,name_string): - if(loc==34196):#europe - return(name_string) - else: - try: - locarray=self.lcl_dict[loc] - aref=int(locarray[6]) - loc_name=locarray[4] - return(self.ref_locs(aref,name_string+","+loc_name)) - #return(loc_name) - except KeyError: - return(name_string) - + if(loc==34196):#europe + return(name_string) + else: + try: + locarray=self.lcl_dict[loc] + aref=int(locarray[6]) + loc_name=locarray[4] + return(self.ref_locs(aref,name_string+","+loc_name)) + #return(loc_name) + except KeyError: + return(name_string) + def decode_chars(self,charstring): - alphabet={ - 0b0010:u" !\#¤%&'()*+,-./", - 0b0011:u"0123456789:;<=>?", - 0b0100:u"@ABCDEFGHIJKLMNO", - 0b0101:u"PQRSTUVWXYZ[\]―_", - 0b0110:u"‖abcdefghijklmno", - 0b0111:u"pqrstuvwxyz{|}¯ ", - 0b1000:u"áàéèíìóòúùÑÇŞßiIJ", - 0b1001:u"âäêëîïôöûüñçşǧıij", - 0b1010:u"ªα©‰Ǧěňőπ€£$←↑→↓", - 0b1011:u"º¹²³±İńűµ¿÷°¼½¾§", - 0b1100:u"ÁÀÉÈÍÌÓÒÚÙŘČŠŽĐĿ", - 0b1101:u"ÂÄÊËÎÏÔÖÛÜřčšžđŀ", - 0b1110:u"ÃÅÆŒŷÝÕØÞŊŔĆŚŹŦð", - 0b1111:u"ãåæœŵýõøþŋŕćśźŧ "}#0xff should not occur (not in standard) (but occured 2017-03-04-9:18 , probably transmission error) - - #charlist=list(charstring) - return_string="" - for i,char in enumerate(charstring): - #split byte - alnr=(ord(char)&0xF0 )>>4 #upper 4 bit - index=ord(char)&0x0F #lower 4 bit - if ord(char)<= 0b00011111:#control code - if ord(char)==0x0D or ord(char)==0x00:#end of message SWR uses: \r\0\0\0 for last block (\0 fill 4 char segment) - #return_string+="\r" - return_string+=char - else: - return_string+="{%02X}"%ord(char)#output control code -# elif ord(char)<= 0b01111111: #real encoding slightly different from ascii -# return_string+=char#use ascii - else: - try: - return_string+=alphabet[alnr][index] - #return_string+=unichr(ord(char))#TODO: properly decide for UTF8 or EBU charset - except KeyError: - return_string+="?%02X?"%ord(char)#symbol not decoded - print("symbol not decoded: "+"?%02X?"%ord(char)+"in string:"+return_string) - pass - return return_string + alphabet={ + 0b0010:u" !\#¤%&'()*+,-./", + 0b0011:u"0123456789:;<=>?", + 0b0100:u"@ABCDEFGHIJKLMNO", + 0b0101:u"PQRSTUVWXYZ[\]―_", + 0b0110:u"‖abcdefghijklmno", + 0b0111:u"pqrstuvwxyz{|}¯ ", + 0b1000:u"áàéèíìóòúùÑÇŞßiIJ", + 0b1001:u"âäêëîïôöûüñçşǧıij", + 0b1010:u"ªα©‰Ǧěňőπ€£$←↑→↓", + 0b1011:u"º¹²³±İńűµ¿÷°¼½¾§", + 0b1100:u"ÁÀÉÈÍÌÓÒÚÙŘČŠŽĐĿ", + 0b1101:u"ÂÄÊËÎÏÔÖÛÜřčšžđŀ", + 0b1110:u"ÃÅÆŒŷÝÕØÞŊŔĆŚŹŦð", + 0b1111:u"ãåæœŵýõøþŋŕćśźŧ "}#0xff should not occur (not in standard) (but occured 2017-03-04-9:18 , probably transmission error) + + #charlist=list(charstring) + return_string="" + for i,char in enumerate(charstring): + #split byte + alnr=(ord(char)&0xF0 )>>4 #upper 4 bit + index=ord(char)&0x0F #lower 4 bit + if ord(char)<= 0b00011111:#control code + if ord(char)==0x0D or ord(char)==0x00:#end of message SWR uses: \r\0\0\0 for last block (\0 fill 4 char segment) + #return_string+="\r" + return_string+=char + else: + return_string+="{%02X}"%ord(char)#output control code +# elif ord(char)<= 0b01111111: #real encoding slightly different from ascii +# return_string+=char#use ascii + else: + try: + return_string+=alphabet[alnr][index] + #return_string+=unichr(ord(char))#TODO: properly decide for UTF8 or EBU charset + except KeyError: + return_string+="?%02X?"%ord(char)#symbol not decoded + print("symbol not decoded: "+"?%02X?"%ord(char)+"in string:"+return_string) + pass + return return_string def color_text(self, text, start,end,textcolor,segmentcolor): - #formatted_text="%s%s%s"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) - #formatted_text="%s%s%s"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) - formatted_text=("%s"*3)% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) - return formatted_text + #formatted_text="%s%s%s"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) + #formatted_text="%s%s%s"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) + formatted_text=("%s"*3)% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) + return formatted_text class rds_parser_table_qt_Widget(QtGui.QWidget): def __init__(self, signals,label,tableobj,showTMC): - #print("gui initializing")self.tableobj.RDS_data["D3A2"] - self.signals = signals - self.tableobj=tableobj - self.showTMC=showTMC - self.signals.DataUpdateEvent.connect(self.display_data) + #print("gui initializing")self.tableobj.RDS_data["D3A2"] + self.signals = signals + self.tableobj=tableobj + self.showTMC=showTMC + self.signals.DataUpdateEvent.connect(self.display_data) """ Creates the QT Range widget """ QtGui.QWidget.__init__(self) layout = Qt.QVBoxLayout() @@ -1058,11 +1065,11 @@ class rds_parser_table_qt_Widget(QtGui.QWidget): #self.colorder=['ID','freq','name','buttons','PTY','AF','time','text','quality'] self.colorder=tableobj.colorder ##button.clicked.connect(self.getDetails) - + layout.addWidget(self.table) - self.table.setHorizontalHeaderLabels(self.colorder) + self.table.setHorizontalHeaderLabels(self.colorder) #self.table.setMaximumHeight(300)#TODO use dynamic value - + button_layout = Qt.QHBoxLayout() codebutton = QtGui.QPushButton("code.interact") codebutton.clicked.connect(self.onCLick) @@ -1085,234 +1092,234 @@ class rds_parser_table_qt_Widget(QtGui.QWidget): #self.freq_label.setTextFormat(QtCore.Qt.RichText) #self.freq_label.setTextFormat(QtCore.Qt.PlainText) self.count_label=QtGui.QLabel("count:") - label_layout.addWidget(self.freq_label) - label_layout.addWidget(self.count_label) + label_layout.addWidget(self.freq_label) + label_layout.addWidget(self.count_label) layout.addLayout(label_layout) #TODO set different minsize if TMC is shown self.setMinimumSize(Qt.QSize(500,40*self.tableobj.nPorts)) if self.showTMC: - self.tmc_message_label=QtGui.QLabel("TMC messages:") - self.event_filter=QtGui.QLineEdit()#QPlainTextEdit ? - self.location_filter=QtGui.QLineEdit(u"Baden-Württemberg") - #self.location_filter=QtGui.QLineEdit(u"") - self.event_filter.returnPressed.connect(self.filterChanged) - self.location_filter.returnPressed.connect(self.filterChanged) - - filter_layout = Qt.QHBoxLayout() - filter_layout.addWidget(QtGui.QLabel("event filter:")) - filter_layout.addWidget(self.event_filter) - filter_layout.addWidget(QtGui.QLabel("location filter:")) - filter_layout.addWidget(self.location_filter) - - layout.addLayout(filter_layout) - layout.addWidget(self.tmc_message_label) - self.logOutput = Qt.QTextEdit() - self.logOutput.setReadOnly(True) - self.logOutput.setLineWrapMode(Qt.QTextEdit.NoWrap) - self.logOutput.setMaximumHeight(150) - font = self.logOutput.font() - font.setFamily("Courier") - font.setPointSize(10) - layout.addWidget(self.logOutput) + self.tmc_message_label=QtGui.QLabel("TMC messages:") + self.event_filter=QtGui.QLineEdit()#QPlainTextEdit ? + self.location_filter=QtGui.QLineEdit(u"Baden-Württemberg") + #self.location_filter=QtGui.QLineEdit(u"") + self.event_filter.returnPressed.connect(self.filterChanged) + self.location_filter.returnPressed.connect(self.filterChanged) + + filter_layout = Qt.QHBoxLayout() + filter_layout.addWidget(QtGui.QLabel("event filter:")) + filter_layout.addWidget(self.event_filter) + filter_layout.addWidget(QtGui.QLabel("location filter:")) + filter_layout.addWidget(self.location_filter) + + layout.addLayout(filter_layout) + layout.addWidget(self.tmc_message_label) + self.logOutput = Qt.QTextEdit() + self.logOutput.setReadOnly(True) + self.logOutput.setLineWrapMode(Qt.QTextEdit.NoWrap) + self.logOutput.setMaximumHeight(150) + font = self.logOutput.font() + font.setFamily("Courier") + font.setPointSize(10) + layout.addWidget(self.logOutput) self.lastResizeTime=0 self.clip = QtGui.QApplication.clipboard() - #self.cb.clear(mode=cb.Clipboard ) - #self.cb.setText("Clipboard Text", mode=cb.Clipboard) + #self.cb.clear(mode=cb.Clipboard ) + #self.cb.setText("Clipboard Text", mode=cb.Clipboard) def filterChanged(self): - print("filter changed") - ef=unicode(self.event_filter.text().toUtf8(), encoding="UTF-8").lower() - lf=unicode(self.location_filter.text().toUtf8(), encoding="UTF-8").lower() - self.logOutput.clear() - #filters=[{"type":"location", "str":u"Baden-Württemberg"}] - filters=[{"type":"location", "str":lf},{"type":"event", "str":ef}] - self.logOutput.append(Qt.QString.fromUtf8(self.tableobj.tmc_messages.getLogString(filters))) - #self.logOutput.append(Qt.QString.fromUtf8(self.tableobj.tmc_messages.getLogString([]))) + print("filter changed") + ef=unicode(self.event_filter.text().toUtf8(), encoding="UTF-8").lower() + lf=unicode(self.location_filter.text().toUtf8(), encoding="UTF-8").lower() + self.logOutput.clear() + #filters=[{"type":"location", "str":u"Baden-Württemberg"}] + filters=[{"type":"location", "str":lf},{"type":"event", "str":ef}] + self.logOutput.append(Qt.QString.fromUtf8(self.tableobj.tmc_messages.getLogString(filters))) + #self.logOutput.append(Qt.QString.fromUtf8(self.tableobj.tmc_messages.getLogString([]))) def keyPressEvent(self, e): - if (e.modifiers() & QtCore.Qt.ControlModifier) and len(self.table.selectedRanges())>0: - selected = self.table.selectedRanges().pop() - selected.leftColumn() - selected.topRow() - if e.key() == QtCore.Qt.Key_C: #copy - try: - qs = self.table.cellWidget(selected.topRow(),selected.leftColumn()).text()#get QString from table - s=re.sub("<.*?>","", str(qs))#remove html tags - self.clip.setText(s) - except Exception as e: - print(e) - print("no text, cant copy") + if (e.modifiers() & QtCore.Qt.ControlModifier) and len(self.table.selectedRanges())>0: + selected = self.table.selectedRanges().pop() + selected.leftColumn() + selected.topRow() + if e.key() == QtCore.Qt.Key_C: #copy + try: + qs = self.table.cellWidget(selected.topRow(),selected.leftColumn()).text()#get QString from table + s=re.sub("<.*?>","", str(qs))#remove html tags + self.clip.setText(s) + except Exception as e: + print(e) + print("no text, cant copy") def insert_empty_row(self): - rowPosition = self.table.rowCount() - self.table.insertRow(rowPosition) - #for col in range(self.table.columnCount()-1):#all labels except in last column -> buttons -# self.table.setCellWidget(rowPosition,col,QtGui.QLabel()) - #initialize labels everywhere: - for col in range(self.table.columnCount()): - self.table.setCellWidget(rowPosition,col,QtGui.QLabel()) - button_layout = Qt.QHBoxLayout() - details_button=QtGui.QPushButton("Detail") - details_button.clicked.connect(functools.partial(self.getDetails, row=rowPosition)) - button_layout.addWidget(details_button) - #2017-03-17 disabled LR buttons - #left_button=QtGui.QPushButton("L") - #left_button.clicked.connect(functools.partial(self.setAudio, row=rowPosition,audio_channel="left")) - #button_layout.addWidget(left_button) - #right_button=QtGui.QPushButton("R") - #right_button.clicked.connect(functools.partial(self.setAudio, row=rowPosition,audio_channel="right")) - #button_layout.addWidget(right_button) - - cellWidget = QtGui.QWidget() - cellWidget.setLayout(button_layout) - button_col=3 - self.table.setCellWidget(rowPosition,button_col,cellWidget) + rowPosition = self.table.rowCount() + self.table.insertRow(rowPosition) + #for col in range(self.table.columnCount()-1):#all labels except in last column -> buttons +# self.table.setCellWidget(rowPosition,col,QtGui.QLabel()) + #initialize labels everywhere: + for col in range(self.table.columnCount()): + self.table.setCellWidget(rowPosition,col,QtGui.QLabel()) + button_layout = Qt.QHBoxLayout() + details_button=QtGui.QPushButton("Detail") + details_button.clicked.connect(functools.partial(self.getDetails, row=rowPosition)) + button_layout.addWidget(details_button) + #2017-03-17 disabled LR buttons + #left_button=QtGui.QPushButton("L") + #left_button.clicked.connect(functools.partial(self.setAudio, row=rowPosition,audio_channel="left")) + #button_layout.addWidget(left_button) + #right_button=QtGui.QPushButton("R") + #right_button.clicked.connect(functools.partial(self.setAudio, row=rowPosition,audio_channel="right")) + #button_layout.addWidget(right_button) + + cellWidget = QtGui.QWidget() + cellWidget.setLayout(button_layout) + button_col=3 + self.table.setCellWidget(rowPosition,button_col,cellWidget) def display_data(self, event): - #pp.pprint(event) - if type(event)==dict and event.has_key('group_count'): - self.count_label.setText("count:%02i, max:%i"%(event['group_count'],event['group_count_max'])) - if type(event)==dict and event.has_key('decoder_frequencies'): - self.freq_label.setText(event['decoder_frequencies']) - if type(event)==dict and event.has_key('TMC_log') and self.showTMC: - tmc_msg=event['TMC_log'] - ef=unicode(self.event_filter.text().toUtf8(), encoding="UTF-8").lower() - lf=unicode(self.location_filter.text().toUtf8(), encoding="UTF-8").lower() - filters=[{"type":"location", "str":lf},{"type":"event", "str":ef}] - if self.tableobj.tmc_messages.matchFilter(tmc_msg,filters): - self.logOutput.append(Qt.QString.fromUtf8(tmc_msg.log_string())) - self.logOutput.append(Qt.QString.fromUtf8(tmc_msg.multi_str())) - if type(event)==dict and event.has_key('TMC_log_str')and self.showTMC: - ef=unicode(self.event_filter.text().toUtf8(), encoding="UTF-8").lower() - lf=unicode(self.location_filter.text().toUtf8(), encoding="UTF-8").lower() - text=unicode(event['TMC_log_str'], encoding="UTF-8").lower() - if not text.find(lf)==-1 and not text.find(ef)==-1: - self.logOutput.append(Qt.QString.fromUtf8(event['TMC_log_str'])) - if type(event)==dict and event.has_key('PI'): - PI=event['PI'] - if not self.PI_to_row.has_key(PI): - self.PI_to_row[PI]=len(self.PI_to_row)#zero for first PI seen, then count up - self.insert_empty_row() - row=self.PI_to_row[PI] - PIcol=self.colorder.index('ID') - self.table.cellWidget(row,PIcol).setText(PI) - - if event.has_key('freq'): - freqcol=self.colorder.index('freq') - item=self.table.cellWidget(row,freqcol) - item.setText(event['freq']) - if event.has_key('wrong_blocks'): - item=self.table.cellWidget(row,self.colorder.index('quality')) - quality_string="%i%% %s"% (100-2*event['wrong_blocks'],event['dots']) - item.setText(quality_string) - if event.has_key('PTY'): - item=self.table.cellWidget(row,self.colorder.index('PTY')) - tt=item.toolTip() - item.setText(event['PTY']) - item.setToolTip(tt) - if event.has_key('flags'): - item=self.table.cellWidget(row,self.colorder.index('PTY')) - item.setToolTip(Qt.QString(event['flags'])) - if event.has_key('string'): - item=self.table.cellWidget(row,event['col']) - item.setText(event['string']) - if event.has_key('tooltip'): - item=self.table.cellWidget(row,event['col']) - item.setToolTip(Qt.QString(event['tooltip'])) - if event.has_key('AF'): - #setAF - PIcol=self.colorder.index('AF') - self.table.cellWidget(row,PIcol).setText(str(event['AF']['number'])) - if event.has_key('PSN'): - #setPSN - PSNcol=self.colorder.index('name') - item=self.table.cellWidget(row,PSNcol) - item.setText(event['PSN']) - if time.time()-self.lastResizeTime > 2:#every 2 seconds - self.table.resizeColumnsToContents() - self.lastResizeTime=time.time() - #end of display-data + #pp.pprint(event) + if type(event)==dict and event.has_key('group_count'): + self.count_label.setText("count:%02i, max:%i"%(event['group_count'],event['group_count_max'])) + if type(event)==dict and event.has_key('decoder_frequencies'): + self.freq_label.setText(event['decoder_frequencies']) + if type(event)==dict and event.has_key('TMC_log') and self.showTMC: + tmc_msg=event['TMC_log'] + ef=unicode(self.event_filter.text().toUtf8(), encoding="UTF-8").lower() + lf=unicode(self.location_filter.text().toUtf8(), encoding="UTF-8").lower() + filters=[{"type":"location", "str":lf},{"type":"event", "str":ef}] + if self.tableobj.tmc_messages.matchFilter(tmc_msg,filters): + self.logOutput.append(Qt.QString.fromUtf8(tmc_msg.log_string())) + self.logOutput.append(Qt.QString.fromUtf8(tmc_msg.multi_str())) + if type(event)==dict and event.has_key('TMC_log_str')and self.showTMC: + ef=unicode(self.event_filter.text().toUtf8(), encoding="UTF-8").lower() + lf=unicode(self.location_filter.text().toUtf8(), encoding="UTF-8").lower() + text=unicode(event['TMC_log_str'], encoding="UTF-8").lower() + if not text.find(lf)==-1 and not text.find(ef)==-1: + self.logOutput.append(Qt.QString.fromUtf8(event['TMC_log_str'])) + if type(event)==dict and event.has_key('PI'): + PI=event['PI'] + if not self.PI_to_row.has_key(PI): + self.PI_to_row[PI]=len(self.PI_to_row)#zero for first PI seen, then count up + self.insert_empty_row() + row=self.PI_to_row[PI] + PIcol=self.colorder.index('ID') + self.table.cellWidget(row,PIcol).setText(PI) + + if event.has_key('freq'): + freqcol=self.colorder.index('freq') + item=self.table.cellWidget(row,freqcol) + item.setText(event['freq']) + if event.has_key('wrong_blocks'): + item=self.table.cellWidget(row,self.colorder.index('quality')) + quality_string="%i%% %s"% (100-2*event['wrong_blocks'],event['dots']) + item.setText(quality_string) + if event.has_key('PTY'): + item=self.table.cellWidget(row,self.colorder.index('PTY')) + tt=item.toolTip() + item.setText(event['PTY']) + item.setToolTip(tt) + if event.has_key('flags'): + item=self.table.cellWidget(row,self.colorder.index('PTY')) + item.setToolTip(Qt.QString(event['flags'])) + if event.has_key('string'): + item=self.table.cellWidget(row,event['col']) + item.setText(event['string']) + if event.has_key('tooltip'): + item=self.table.cellWidget(row,event['col']) + item.setToolTip(Qt.QString(event['tooltip'])) + if event.has_key('AF'): + #setAF + PIcol=self.colorder.index('AF') + self.table.cellWidget(row,PIcol).setText(str(event['AF']['number'])) + if event.has_key('PSN'): + #setPSN + PSNcol=self.colorder.index('name') + item=self.table.cellWidget(row,PSNcol) + item.setText(event['PSN']) + if time.time()-self.lastResizeTime > 2:#every 2 seconds + self.table.resizeColumnsToContents() + self.lastResizeTime=time.time() + #end of display-data def printProfile(self): - self.tableobj.print_results() + self.tableobj.print_results() def switchMode(self): - #print("mode switch message sent") - send_pmt = pmt.pmt_to_python.pmt_from_dict({"cmd":"switch mode"}) - #send_pmt = pmt.string_to_symbol("switch mode") - self.tableobj.message_port_pub(pmt.intern('ctrl'), send_pmt) + #print("mode switch message sent") + send_pmt = pmt.pmt_to_python.pmt_from_dict({"cmd":"switch mode"}) + #send_pmt = pmt.string_to_symbol("switch mode") + self.tableobj.message_port_pub(pmt.intern('ctrl'), send_pmt) def saveData(self): - filename="RDS_data_"+str(datetime.now())+".txt" - f=open(self.tableobj.workdir+filename,"w") - rds_data=copy.deepcopy(self.tableobj.RDS_data) - for PI in sorted(rds_data): - try: - del rds_data[PI]['PSN_valid'] - del rds_data[PI]['RT_valid'] - except KeyError: - pass - f.write("Data:%s"%pp.pformat(rds_data)) - f.write("\n\nIn House Data:\n%s"%pp.pformat(self.tableobj.IH_data)) - f.close() - print("data saved in file %s"%filename) + filename="RDS_data_"+str(datetime.now())+".txt" + f=open(self.tableobj.workdir+filename,"w") + rds_data=copy.deepcopy(self.tableobj.RDS_data) + for PI in sorted(rds_data): + try: + del rds_data[PI]['PSN_valid'] + del rds_data[PI]['RT_valid'] + except KeyError: + pass + f.write("Data:%s"%pp.pformat(rds_data)) + f.write("\n\nIn House Data:\n%s"%pp.pformat(self.tableobj.IH_data)) + f.close() + print("data saved in file %s"%filename) def showIHdata(self): - view=Qt.QDialog() - l=QtGui.QLabel("In House Data:\n%s"%pp.pformat(self.tableobj.IH_data)) - l.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse |QtCore.Qt.TextSelectableByKeyboard) - l.setWordWrap(True) - #self.IH_data - layout=Qt.QVBoxLayout() - layout.addWidget(l) - view.setLayout(layout) - view.exec_() + view=Qt.QDialog() + l=QtGui.QLabel("In House Data:\n%s"%pp.pformat(self.tableobj.IH_data)) + l.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse |QtCore.Qt.TextSelectableByKeyboard) + l.setWordWrap(True) + #self.IH_data + layout=Qt.QVBoxLayout() + layout.addWidget(l) + view.setLayout(layout) + view.exec_() def setAudio(self,row,audio_channel): - - PIcol=self.colorder.index('ID') - PI=str(self.table.cellWidget(row,PIcol).text()) - freq=int(self.tableobj.RDS_data[PI]['AF']['main']) - #print("setaudio row:%i, chan:%s, PI:%s,freq:%i"%(row,audio_channel,PI,freq)) - send_pmt = pmt.pmt_to_python.pmt_from_dict({"cmd":"set_audio_freq","chan":audio_channel,"freq":freq}) - self.tableobj.message_port_pub(pmt.intern('ctrl'), send_pmt) - #catch: - #print("no freq, cant set decoder")#show notification? popup: too intrusive, log: maybe not visible, other possibility? - #print("freq not in RX BW")#automatically shift freq-tune? + + PIcol=self.colorder.index('ID') + PI=str(self.table.cellWidget(row,PIcol).text()) + freq=int(self.tableobj.RDS_data[PI]['AF']['main']) + #print("setaudio row:%i, chan:%s, PI:%s,freq:%i"%(row,audio_channel,PI,freq)) + send_pmt = pmt.pmt_to_python.pmt_from_dict({"cmd":"set_audio_freq","chan":audio_channel,"freq":freq}) + self.tableobj.message_port_pub(pmt.intern('ctrl'), send_pmt) + #catch: + #print("no freq, cant set decoder")#show notification? popup: too intrusive, log: maybe not visible, other possibility? + #print("freq not in RX BW")#automatically shift freq-tune? def getDetails(self,row): - PIcol=self.colorder.index('ID') - PI=str(self.table.cellWidget(row,PIcol).text()) - view = chart.DialogViewer() - if self.tableobj.PI_dict.has_key(PI) and self.tableobj.PI_dict[PI]>3:#dont print piechart if no packets received (detected via EON) - table=chart.DataTable() - table.addColumn('groupType') - table.addColumn('numPackets') - blockcounts=copy.deepcopy(self.tableobj.RDS_data[PI]['blockcounts']) - del blockcounts['any'] - #lambda function removes last character of PI string (A or B) and sorts based on integer valure of number in front - for key in sorted(blockcounts,key=lambda elem: int(elem[0:-1])): - count=blockcounts[key] - table.addRow([key+": "+str(count),count]) - mychart=chart.PieChart(table) - view.setGraph(mychart) - #view.resize(360, 240) - #view.resize(380, 550) - rds_data=copy.deepcopy(self.tableobj.RDS_data[PI]) - try: - del rds_data['blockcounts'] - del rds_data['PSN_valid'] - del rds_data["RT_0"]['RT_valid'] - del rds_data["RT_1"]['RT_valid'] - rds_data['internals']['RT_history']=["".join(rt) for rt in rds_data['internals']['RT_history']]#combine char lists into strings (more compact) - except KeyError: - pass - l=QtGui.QLabel("Data:%s"%pp.pformat(rds_data)) - l.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse |QtCore.Qt.TextSelectableByKeyboard) - l.setWordWrap(True) - #l=QtGui.QLabel("Data:") - - #view.layout().addWidget(l) - - scrollArea = QtGui.QScrollArea(self) + PIcol=self.colorder.index('ID') + PI=str(self.table.cellWidget(row,PIcol).text()) + view = chart.DialogViewer() + if self.tableobj.PI_dict.has_key(PI) and self.tableobj.PI_dict[PI]>3:#dont print piechart if no packets received (detected via EON) + table=chart.DataTable() + table.addColumn('groupType') + table.addColumn('numPackets') + blockcounts=copy.deepcopy(self.tableobj.RDS_data[PI]['blockcounts']) + del blockcounts['any'] + #lambda function removes last character of PI string (A or B) and sorts based on integer valure of number in front + for key in sorted(blockcounts,key=lambda elem: int(elem[0:-1])): + count=blockcounts[key] + table.addRow([key+": "+str(count),count]) + mychart=chart.PieChart(table) + view.setGraph(mychart) + #view.resize(360, 240) + #view.resize(380, 550) + rds_data=copy.deepcopy(self.tableobj.RDS_data[PI]) + try: + del rds_data['blockcounts'] + del rds_data['PSN_valid'] + del rds_data["RT_0"]['RT_valid'] + del rds_data["RT_1"]['RT_valid'] + rds_data['internals']['RT_history']=["".join(rt) for rt in rds_data['internals']['RT_history']]#combine char lists into strings (more compact) + except KeyError: + pass + l=QtGui.QLabel("Data:%s"%pp.pformat(rds_data)) + l.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse |QtCore.Qt.TextSelectableByKeyboard) + l.setWordWrap(True) + #l=QtGui.QLabel("Data:") + + #view.layout().addWidget(l) + + scrollArea = QtGui.QScrollArea(self) scrollArea.setWidgetResizable(True) scrollArea.setWidget(l) - view.layout().addWidget(scrollArea) - view.setWindowTitle(self.tableobj.RDS_data[PI]["PSN"]) - view.exec_() + view.layout().addWidget(scrollArea) + view.setWindowTitle(self.tableobj.RDS_data[PI]["PSN"]) + view.exec_() def onCLick(self): - print("button clicked") - code.interact(local=locals()) + print("button clicked") + code.interact(local=locals()) if __name__ == "__main__": from PyQt4 import Qt import sys @@ -1328,5 +1335,3 @@ if __name__ == "__main__": sys.exit(app.exec_()) widget = None - - diff --git a/python/rds_parser_table_qt.py.bak b/python/rds_parser_table_qt.py.bak new file mode 100644 index 0000000..3202f3a --- /dev/null +++ b/python/rds_parser_table_qt.py.bak @@ -0,0 +1,1346 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2016 <+YOU OR YOUR COMPANY+>. +# +# This is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# +from __future__ import print_function#print without newline print('.', end="") +import numpy +from gnuradio import gr +import pmt,functools,csv,md5,collections,copy,sqlite3,atexit,time,re,sys +#old imports: folium +from datetime import datetime +from datetime import timedelta +import crfa.chart as chart +from crfa.tmc_classes import tmc_dict,tmc_message + +from PyQt4 import Qt, QtCore, QtGui +import pprint,code,pickle#for easier testing +pp = pprint.PrettyPrinter() +import cProfile, pstats, StringIO #for profiling +pr = cProfile.Profile() + +#from threading import Timer#to periodically save DB + +from PyQt4.QtCore import QObject, pyqtSignal +from bitstring import BitArray + +language="de"#currently supported: de, en (both partially) + + +class rds_parser_table_qt_Signals(QObject): + DataUpdateEvent = QtCore.pyqtSignal(dict) + def __init__(self, parent=None): + super(QtCore.QObject, self).__init__() +class rds_parser_table_qt(gr.sync_block):#START + """ + docstring for block qtguitest + """ + def goodbye(self): + self.clean_data_and_commit_db() + print("quitting rds parser table, closing db") + if self.writeDB: + #self.db.commit() + self.db.close() + def __init__(self,signals,nPorts,slot,freq,log,debug,workdir,writeDB,showTMC): + gr.sync_block.__init__(self, + name="RDS Table", + in_sig=None, + out_sig=None) + if nPorts==1: + self.message_port_register_in(pmt.intern('in')) + self.set_msg_handler(pmt.intern('in'), functools.partial(self.handle_msg, port=0)) + else: + for i in range(nPorts): + self.message_port_register_in(pmt.intern('in%d'%i)) + self.set_msg_handler(pmt.intern('in%d'%i), functools.partial(self.handle_msg, port=i)) + self.nPorts=nPorts + self.message_port_register_in(pmt.intern('freq')) + self.set_msg_handler(pmt.intern('freq'), self.set_freq) + self.message_port_register_out(pmt.intern('ctrl')) + self.message_port_register_out(pmt.intern('tmc_raw')) + + self.log=log + self.debug=debug + self.writeDB=writeDB + self.showTMC=showTMC + self.signals=signals + self.RDS_data={} + self.change_freq_tune=slot + self.tuning_frequency=int(freq) + self.printcounter=0 + self.ODA_application_names={} + self.TMC_data={} + self.IH_data={} + self.decoder_frequencies={} + self.decoders=[] + for i in range(nPorts): + self.decoders.append({'synced':False,'freq':None}) + #self.decoder_synced={} + #self.colorder=['ID','freq','name','PTY','AF','time','text','quality','buttons'] + self.colorder=['ID','freq','name','buttons','PTY','AF','time','text','quality','RT+'] + self.workdir=workdir + self.PI_dict={}#contains PI:numpackets (string:integer) + self.tmc_messages=tmc_dict() + + if self.writeDB: + #create new DB file + db_name=workdir+'RDS_data'+datetime.now().strftime("%Y%m%d_%H%M%S")+'.db' + db=sqlite3.connect(db_name, check_same_thread=False) + self.db=db + #create tables + try: + db.execute('''CREATE TABLE stations + (PI text PRIMARY KEY UNIQUE,PSN text, freq real, PTY text,TP integer)''') + db.execute('''CREATE TABLE groups + (time text,PI text,PSN text, grouptype text,content blob)''') + db.execute('''CREATE TABLE data + (time text,PI text,PSN text, dataType text,data blob)''') + db.execute('''CREATE TABLE grouptypeCounts + (PI text,grouptype text,count integer,unique (PI, grouptype))''') + db.execute('''CREATE TABLE TMC + (hash text PRIMARY KEY UNIQUE,time text,PI text, F integer,event integer,location integer,DP integer,div integer,dir integer,extent integer,text text,multi text,rawmgm text)''') + db.commit() + + except sqlite3.OperationalError: + print("ERROR: tables already exist") + + #self.dbc.execute('''CREATE TABLE rtp + # (time text,PI text,rtp_string text)''') + reader = csv.reader(open(self.workdir+'RDS_ODA-AIDs_names_only.csv'), delimiter=',', quotechar='"') + reader.next()#skip header + for row in reader: + self.ODA_application_names[int(row[0])]=row[1] + #read location code list: + reader = csv.reader(open(self.workdir+'LCL15.1.D-160122_utf8.csv'), delimiter=';', quotechar='"') + reader.next()#skip header + self.lcl_dict=dict((int(rows[0]),rows[1:]) for rows in reader) + #read RT+ class name list: + reader = csv.reader(open(self.workdir+'RTplus_classnames.csv'), delimiter=',', quotechar='"') + reader.next()#skip header + self.rtp_classnames=dict((int(rows[0]),rows[1]) for rows in reader) + #read TMC-event list + reader = csv.reader(open(self.workdir+'event-list_with_forecast_sort.csv'), delimiter=',', quotechar='"') + reader.next()#skip header + self.ecl_dict=dict((int(rows[0]),rows[1:]) for rows in reader) + #Code,Text CEN-English,Text (German),Text (German) kein Quantifier,Text (Quantifier = 1),Text (Quantifier >1),N,Q,T,D,U,C,R ,Comment + #N:nature (blank): information, F:forecast, S:silent + #Q:quantifier type: (0..12) or blank (no quantifier) + #T:duration type: D:dynamic, L:long lasting, in brackets or if time-of-day quantifier (no 7) is used in message -> no display, only for management + #D:direction: 1:unidirectional, 2:bidirectional + #U:urgency: blank: normal, X:extremely urgent, U:urgent + #C: update class: + + #read update classes + reader = csv.reader(open(self.workdir+'tmc_update_class_names.csv'), delimiter=',', quotechar='"') + reader.next()#skip header, "code(C),english,german" + if language=="de": + self.tmc_update_class_names=dict((int(rows[0]),rows[2]) for rows in reader)#german names + else: + self.tmc_update_class_names=dict((int(rows[0]),rows[1]) for rows in reader)#english names + #read supplementary information code list + reader = csv.reader(open(self.workdir+'label6-supplementary-information-codes.csv'), delimiter=',', quotechar='"') + reader.next()#skip header, "code,english,german" + if language=="de": + self.label6_suppl_info=dict((int(rows[0]),rows[2]) for rows in reader)#german + else: + self.label6_suppl_info=dict((int(rows[0]),rows[1]) for rows in reader)#english + #read PTY list + f=open(self.workdir+'pty-list.csv') + reader = csv.reader(f, delimiter=',', quotechar='"') + reader.next()#skip header + self.pty_dict=dict((int(rows[0]),rows[1]) for rows in reader) + f.close() + self.minute_count=0 + self.minute_count_max=0 + self.minute_count_timer=time.time() + self.save_data_timer=time.time() + + atexit.register(self.goodbye) + + def clean_data_and_commit_db(self): + for PI in self.PI_dict: + self.PI_dict[PI]-=1 + #print(self.PI_dict) + if self.writeDB: + self.db.commit() + f=open(self.workdir+'google_maps_markers.js', 'w') + markerstring=self.tmc_messages.getMarkerString() + markerstring+='\n console.log("loaded "+markers.length+" markers")' + markerstring+='\n document.getElementById("errorid").innerHTML = "loaded "+markers.length+" markers";' + f.write(markerstring) + f.close() + def update_freq(self): + # " " is a tab character + message_string="decoder frequencies:" + for num in self.decoder_frequencies: + freq=self.decoder_frequencies[num] + if self.decoders[num]['synced']: + message_string+="  %i:%0.1fM"% (num,freq/1e6) + #print("'color:green'>%i:%0.1fM"% (num,freq/1e6)) + else:#elif self.decoders[num]['synced']==False: + #print("'color:red'>%i:%0.1fM"% (num,freq/1e6)) + message_string+="  %i:%0.1fM"% (num,freq/1e6) + message_string+="  tuned frequency:%0.1fM"%(self.tuning_frequency/1e6) + self.signals.DataUpdateEvent.emit({'decoder_frequencies':message_string}) + #print(message_string) + #self.signals.DataUpdateEvent.emit({'row':decoder_num,'freq':freq_str}) + #print("nr:%i freq:%s"%(tgtnum,freq_str)) + def set_freq_tune(self,freq): + self.tuning_frequency=int(freq) + self.update_freq() + def set_freq(self,msg): + m = pmt.symbol_to_string(msg) + decoder_num=int(m.split()[0])-1#msgs are 1-indexed, decoder_num is 0-indexed + freq_str=m.split()[1] + try: + freq=float(freq_str) + self.decoder_frequencies[decoder_num]=freq + freq_str="%i:%0.1fM"% (decoder_num,freq/1e6) + except ValueError: + pass#leave string as is + self.update_freq() + def init_data_for_PI(self,PI): + self.RDS_data[PI]={} + #self.RDS_data[PI]["blockcounts"]={}# no defaults (works aswell) + #defaults are to keep colors in piechart consistent between stations: + + coverage_area={"0":"local,","1":"international,","2":"national,","3":"supranational,"} + pi_str="" + if list(PI)[1] in "456789ABCDEF": + pi_str+="regional, " + else: + pi_str+=coverage_area[list(PI)[1]] + bundeslaender={"0":"Baden-Württemberg","1":"Bayern","2":"Berlin","3":"Brandenburg","4":"Bremen und Bremerhaven", + "5":"Hamburg","6":"Hessen","7":"Mecklenburg-Vorpommern","8":"Niedersachsen","9":"Nordrhein-Westfalen","A":"Rheinland-Pfalz", + "B":"Saarland","C":"Sachsen","D":"Sachsen-Anhalt","E":"Schleswig-Holstein","F":"Thüringen"} + if list(PI)[0] in "D1": + pi_str+="germany?, " + pi_str+=bundeslaender[list(PI)[2]]+", " + pi_str+="NR:"+list(PI)[3] + self.RDS_data[PI]["CC"]=list(PI)[0] + self.RDS_data[PI]["PI-meaning"]=pi_str + self.RDS_data[PI]["blockcounts"]={"0A":0,"1A":0,"2A":0,"3A":0,"4A":0,"6A":0,"8A":0,"12A":0,"14A":0} + self.RDS_data[PI]["blockcounts"]["any"]=0 + self.RDS_data[PI]["AID_list"]={} + self.RDS_data[PI]["PSN"]="_"*8 + self.RDS_data[PI]["PSN_valid"]=[False]*8 + self.RDS_data[PI]["AF"]={"set":set(),"rxset":set()} + self.RDS_data[PI]["TP"]=-1 + self.RDS_data[PI]["TA"]=-1 + self.RDS_data[PI]["PTY"]="" + self.RDS_data[PI]["DI"]=[2,2,2,2] + self.RDS_data[PI]["internals"]={"last_rt_tooltip":"","unfinished_TMC":{},"last_valid_rt":"","last_valid_psn":"","RT_history":[]} + self.RDS_data[PI]["time"]={"timestring":"88:88","datestring":"00-00-0000","datetime":None} + def handle_msg(self, msg, port):#port from 0 to 3 + if pmt.to_long(pmt.car(msg))==1L: + data=pmt.to_python(pmt.cdr(msg)) + #print("port:%i, data: %s"%(port,data)) + self.decoders[port]['synced']=data + self.update_freq() + else: #elif pmt.to_long(pmt.car(msg))==0L + array=pmt.to_python(msg)[1] + + if time.time()-self.save_data_timer > 10:#every 10 seconds + self.save_data_timer=time.time() + self.clean_data_and_commit_db() + + if time.time()-self.minute_count_timer > 3:#every 3 second + self.minute_count_max=self.minute_count + self.signals.DataUpdateEvent.emit({'group_count':self.minute_count,'group_count_max':self.minute_count_max}) + self.minute_count=0 + self.minute_count_timer=time.time() + #pr.enable()#disabled-internal-profiling + self.minute_count+=1 + #self.signals.DataUpdateEvent.emit({'group_count':self.minute_count,'group_count_max':self.minute_count_max}) + if self.writeDB: + #db=sqlite3.connect(self.db_name) + db=self.db + + + groupNR=array[2]&0b11110000 + groupVar=array[2]&0b00001000 + if (groupVar == 0): + groupType=str(groupNR >> 4)+"A" + else: + groupType=str(groupNR >> 4)+"B" + #if self.debug: + #PI=str(port)+"_%02X%02X" %(array[0],array[1]) + #else: + #PI="%02X%02X" %(array[0],array[1]) + PI="%02X%02X" %(array[0],array[1]) + TP=(array[2]>>2)&0x1 + block2=(array[2]<<8)|(array[3]) #block2 + PTY=(block2>>5)&0x1F + wrong_blocks=int(array[12]) + + try: + self.PI_dict[PI]+=1 + except KeyError: + pass + + if not self.RDS_data.has_key(PI):#station invalid/new + if not self.PI_dict.has_key(PI):#1st group + self.PI_dict[PI]=1 + return#dont decode further if not yet valid + elif self.PI_dict[PI]>2:#count station as valid if more than 2 packets received + self.init_data_for_PI(PI)#initialize dict for station + if self.log: + print("found station %s"%PI) + else: + return#dont decode further if not yet valid + + if self.decoder_frequencies.has_key(port): + freq=self.decoder_frequencies[port] + freq_str="%i:%0.1fM"% (port,freq/1e6) + self.RDS_data[PI]["tuned_freq"]=freq + #self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) + if self.RDS_data[PI]["blockcounts"].has_key(groupType): + self.RDS_data[PI]["blockcounts"][groupType] +=1 #increment + else: + self.RDS_data[PI]["blockcounts"][groupType] = 1 #initialize (1st group of this type) + self.RDS_data[PI]["blockcounts"]["any"]+=1 + if self.RDS_data[PI]["blockcounts"]["any"]==5: + self.RDS_data[PI]["blockcounts"]["any"]=0 + if self.writeDB: + t=(str(PI),groupType,self.RDS_data[PI]["blockcounts"][groupType])#TODO only update DB every few seconds + db.execute("INSERT OR REPLACE INTO grouptypeCounts (PI,grouptype,count) VALUES (?,?,?)",t) + dots="."*self.RDS_data[PI]["blockcounts"]["any"] + self.RDS_data[PI]["TP"]=TP + self.RDS_data[PI]["PTY"]=self.pty_dict[PTY] + + self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'PTY':self.pty_dict[PTY],'TP':TP,'wrong_blocks':wrong_blocks,'dots':dots}) + + + + #add any received groups to DB (slow) + #content="%02X%02X%02X%02X%02X" %(array[3]&0x1f,array[4],array[5],array[6],array[7]) + #t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],groupType,content) + #db.execute("INSERT INTO groups VALUES (?,?,?,?,?)",t) + + if (groupType == "0A"):#AF PSN + adr=array[3]&0b00000011 + segment=self.decode_chars(chr(array[6])+chr(array[7])) + d=(array[3]>>2)&0x1 + self.RDS_data[PI]["DI"][3-adr]=d#decoder information + #DI[0]=d0 0=Mono 1=Stereo + #d1 Not artificial head Artificial head + #d2 Not compressed Compressed + #d3 Static PTY Dynamic PTY + TA=(array[3]>>4)&0x1 + MS=(array[3]>>3)&0x1 + self.RDS_data[PI]["TA"]=TA + #style='font-family:Courier New;color:%s' + flag_string="TP:%i, TA:%i, MS:%i, DI:%s"%(TP,TA,MS,str(self.RDS_data[PI]["DI"])) + pty_colored=self.RDS_data[PI]["PTY"] + if TP==1: + if TA==1: + color="red" + elif TA==0: + color="green" + else: + color="yellow" + pty_colored="%s"%(color,self.RDS_data[PI]["PTY"]) + + self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'flags':flag_string,'PTY':pty_colored}) + + #224 1110 0000 = no AF + #225 1110 0001 = 1AF + #249 1111 1001 = 25AF + fillercode=205#1100 1101 + if not self.RDS_data[PI]["AF"].has_key("main") and self.RDS_data[PI].has_key("tuned_freq"): + #if self.RDS_data[PI].has_key("tuned_freq"):#update main freq even if one exists -> DB problem + freq=self.decode_AF_freq(array[4]) + if freq==self.RDS_data[PI]["tuned_freq"]: + self.RDS_data[PI]["AF"]["main"]=freq + if self.log: + print("main frequency found in 0A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq/1e6)) + freq_str="0A:%0.1fM"% (freq/1e6) + self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) + t=(PI,self.RDS_data[PI]["PSN"],float(freq),self.RDS_data[PI]["PTY"],int(self.RDS_data[PI]["TP"])) + if self.writeDB: + db.execute("INSERT INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t) + freq=self.decode_AF_freq(array[5]) + if freq==self.RDS_data[PI]["tuned_freq"]: + self.RDS_data[PI]["AF"]["main"]=freq + if self.log: + print("main frequency found in 0A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq/1e6)) + freq_str="0A:%0.1fM"% (freq/1e6) + self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) + t=(PI,self.RDS_data[PI]["PSN"],float(freq),self.RDS_data[PI]["PTY"],int(self.RDS_data[PI]["TP"])) + if self.writeDB: + db.execute("INSERT INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t) + if self.RDS_data[PI].has_key("tuned_freq") :#TODO add secondary freqs + freq=self.decode_AF_freq(array[4]) + diff=abs(freq-self.RDS_data[PI]["tuned_freq"]) + if diff<100000: + self.RDS_data[PI]["AF"]["rxset"].add(freq) + freq=self.decode_AF_freq(array[5]) + diff=abs(freq-self.RDS_data[PI]["tuned_freq"]) + if diff<100000: + self.RDS_data[PI]["AF"]["rxset"].add(freq) + + if(array[4]>= 224 and array[4]<= 249): + #print("AF1 detected") + self.RDS_data[PI]["AF"]['number']=array[4]-224 + #self.RDS_data[PI]["AF"]['main']=self.decode_AF_freq(array[5]) + self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'AF':self.RDS_data[PI]["AF"]}) + if(array[5]>= 224 and array[5]<= 249): + print("AF2 detected (shouldn't happen) %s"%array[5]) + + + #add frequencies to set + self.RDS_data[PI]["AF"]["set"].add(self.decode_AF_freq(array[4])) + self.RDS_data[PI]["AF"]["set"].add(self.decode_AF_freq(array[5])) + try: + self.RDS_data[PI]["AF"]["set"].remove(0)#remove control characters + except KeyError: + pass + + name_list=list(self.RDS_data[PI]["PSN"]) + if (name_list[adr*2:adr*2+2]==list(segment)):#segment already there + segmentcolor="green" + elif(name_list[adr*2:adr*2+2]==['_']*2): #segment new + segmentcolor="orange" + name_list[adr*2:adr*2+2]=segment + else:#name changed (böse) + segmentcolor="red" + name_list=['_']*8 #reset name + name_list[adr*2:adr*2+2]=segment + #reset stored text: + self.RDS_data[PI]["PSN"]="_"*8 + self.RDS_data[PI]["PSN_valid"]=[False]*8 + self.RDS_data[PI]["PSN_valid"][adr*2:adr*2+2]=[True] *2 + self.RDS_data[PI]["PSN"]="".join(name_list) + #determine if text is valid + valid=True + for i in range(0,8): + if (not self.RDS_data[PI]["PSN_valid"][i]): + valid = False + if(valid): + #textcolor="black" + textcolor=""#use default color (white if background is black) + if not self.RDS_data[PI]["internals"]["last_valid_psn"]==self.RDS_data[PI]["PSN"]:#ignore duplicates + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"PSN_valid",self.RDS_data[PI]["PSN"]) + if self.writeDB: + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + t=(self.RDS_data[PI]["PSN"],PI) + if self.writeDB: + db.execute("UPDATE OR IGNORE stations SET PSN=? WHERE PI IS ?",t) + self.RDS_data[PI]["internals"]["last_valid_psn"]=self.RDS_data[PI]["PSN"] + else: + textcolor="gray" + formatted_text=self.color_text(self.RDS_data[PI]["PSN"],adr*2,adr*2+2,textcolor,segmentcolor) + self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'PSN':formatted_text}) + elif (groupType == "1A"):#PIN programme item number + #wer nutzt 1A gruppen? + #antenne1: variants: 0(ECC), + PIN=(array[6]<<8)|(array[7]) + SLC=(array[4]<<8)|(array[5])&0xfff#slow labeling code + radio_paging=array[3]&0x1f + LA=array[4]>>7#linkage actuator + variant=(array[4]>>4)&0x7 + PIN_day=(PIN>>11)&0x1f + PIN_hour=(PIN>>6)&0x1f + PIN_minute=PIN&0x3f + PIN_valid= PIN_day in range(1,32) and PIN_hour in range(0,24) and PIN_minute in range(0,60) + if PIN_valid: + self.RDS_data[PI]["PIN"]=[PIN_day,PIN_hour,PIN_minute] + data_string="variant:%i,SLC:%04X,PIN (valid):%s "%(variant,SLC,str([PIN_day,PIN_hour,PIN_minute])) + else: + data_string="variant:%i,SLC:%04X,PIN:%04X "%(variant,SLC,PIN) + #%02X%02X%02X%02X%02X + + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"PIN",data_string) + if self.writeDB: + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + if self.debug and not variant==0:#print if not seen before + print("PI:%s PSN:%s uses variant %i of 1A"%(PI,self.RDS_data[PI]["PSN"],variant)) + if variant==0: + paging=array[4]&0xf + extended_country_code=array[5] + self.RDS_data[PI]["ECC"]=extended_country_code + if self.debug: + print("1A variant 0: PI:%s PSN:%s,ECC:%s"%(PI,self.RDS_data[PI]["PSN"],hex(extended_country_code))) + elif variant==1: + TMC_info=SLC + elif variant==2: + paging_info=SLC + elif variant==3: + language_codes=SLC + if self.debug: + print("PI:%s PSN:%s,language_codes:%s"%(PI,self.RDS_data[PI]["PSN"],hex(language_codes))) + elif variant==6: + #for use by broadcasters + if self.debug: + print("PI:%s PSN:%s uses variant 6 of 1A"%(PI,self.RDS_data[PI]["PSN"])) + elif variant==7: + ESW_channel_identification=SLC + #end of 1A decode + elif (groupType == "2A"):#RT radiotext + if(not self.RDS_data[PI].has_key("RT_0")):#initialize variables + self.RDS_data[PI]["RT_0"]={"RT":"_"*64,"RT_valid":[False]*64,"RT_all_valid":False} + self.RDS_data[PI]["RT_1"]={"RT":"_"*64,"RT_valid":[False]*64,"RT_all_valid":False} + #self.RDS_data[PI]["RT"]="_"*64 + #self.RDS_data[PI]["RT_valid"]=[False]*64 + #self.RDS_data[PI]["RT_all_valid"]=False + self.RDS_data[PI]["RT_last_ab_flag"]=2 + + adr= array[3]&0b00001111 + ab_flag=(array[3]&0b00010000)>>4 + #print("PI:%s, AB:%i"%(PI,ab_flag)) + + + #if self.RDS_data[PI]["RT_last_ab_flag"] !=ab_flag:#AB flag changed -> clear text + # self.RDS_data[PI]["RT"]="_"*64 + # self.RDS_data[PI]["RT_valid"]=[False]*64 + # self.RDS_data[PI]["RT_last_ab_flag"] =ab_flag + self.RDS_data[PI]["RT_last_ab_flag"] =ab_flag + + #segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7])) + segment=chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7])#EDIT:latedecode + + #print("RT:adress: %d, segment:%s"%(adr,segment)) + #self.signals.DataUpdateEvent.emit({'col':5,'row':port,'PI':PI,'groupType':groupType,'adress':adr,'segment':segment}) + text_list=list(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]) + #determine text length: + try: + text_end=text_list.index('\r') + except ValueError: + text_end=64 #assume whole string is important + pass + predicted=False + if (text_list[adr*4:adr*4+4]==list(segment)):#segment already there + segmentcolor="green" + elif (text_list[adr*4:adr*4+4]==['_']*4):#segment new + segmentcolor="orange" + text_list[adr*4:adr*4+4]=segment + else: + segmentcolor="red" + text_list=['_']*64 #clear text + text_list[adr*4:adr*4+4]=segment + #reset stored text: + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="_"*64 + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"]=[False]*64 + #predict RT from last texts: + for rt in self.RDS_data[PI]["internals"]["RT_history"]: + if rt[adr*4:adr*4+4]==list(segment): + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="".join(rt) + predicted=True + + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"][adr*4:adr*4+4]=[True] *4 + if not predicted: + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="".join(text_list) + + #determine if (new) text is valid + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"]=True + for i in range(0,text_end): + if (not self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"][i]): + self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"] = False + if(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"]): + #textcolor="black" + textcolor=""#use default color (white if background is black) + l=list(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]) + rt="".join(l[0:text_end])#remove underscores(default symbol) after line end marker + if not self.RDS_data[PI]["internals"]["last_valid_rt"]==rt:#ignore duplicates #TODO add 2nd order duplicates ABAB + self.RDS_data[PI]["internals"]["RT_history"].append(l) + if len(self.RDS_data[PI]["internals"]["RT_history"])>10:#only store last 10 RTs + self.RDS_data[PI]["internals"]["RT_history"].pop(0) + if self.writeDB: + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT",self.decode_chars(rt)) + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + self.RDS_data[PI]["internals"]["last_valid_rt"]=rt + try:#save rt+ if it exist + if self.writeDB: + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT+",self.decode_chars(str(self.RDS_data[PI]["RT+"]))) + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + except KeyError: + pass#no rt+ -> dont save + else: + textcolor="gray" + display_text=self.decode_chars(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"].split("\r")[0]) + formatted_text=self.color_text(display_text,adr*4,adr*4+4,textcolor,segmentcolor) + rtcol=self.colorder.index('text') + self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'string':formatted_text}) + + elif (groupType == "3A"):#ODA announcements (contain application ID "AID") + AID=(array[6]<<8)|(array[7])#combine 2 bytes into 1 block + app_data=(array[4]<<8)|(array[5])#content defined by ODA-app + app_group_raw=array[3]&0x1f #group type in which this app is sent + if (app_group_raw&0x1 == 0): + app_group=str(app_group_raw >> 1)+"A" + else: + app_group=str(app_group_raw >> 1)+"B" + + if not self.RDS_data[PI]["AID_list"].has_key(AID):#new ODA found + try: + app_name=self.ODA_application_names[AID] + except KeyError: + if self.debug: + print("ERROR: ODA-app-id (AID) '%i' not found in list on station %s, app group:%s"%(AID,app_group,PI)) + app_name="unknown" + self.RDS_data[PI]["AID_list"][AID]={} + self.RDS_data[PI]["AID_list"][AID]["groupType"]=app_group + self.RDS_data[PI]["AID_list"][AID]["app_name"]=app_name + self.RDS_data[PI]["AID_list"][AID]["app_data"]=app_data + if AID==52550:#TMC alert-c initialize + self.RDS_data[PI]["AID_list"][AID]["provider name"]="________" + if self.log: + print("new ODA: AID:%i, name:'%s', app_group:%s, station:%s" %(AID,app_name,app_group,PI)) + #decode 3A group of TMC + if AID==52550:#TMC alert-c (continuously update) + variant=app_data>>14 + if variant==0: + self.RDS_data[PI]["AID_list"][AID]["LTN"]=(app_data>>6)&0x3f#location table number (6 bits) + self.RDS_data[PI]["AID_list"][AID]["AFI"]=(app_data>>5)&0x1#alternative frequency indicator + self.RDS_data[PI]["AID_list"][AID]["M"]=(app_data>>4)&0x1#transmission mode indicator + #Message Geographical Scope: + self.RDS_data[PI]["AID_list"][AID]["scope"]="" + if (app_data>>3)&0x1==1: + self.RDS_data[PI]["AID_list"][AID]["scope"]+="I"#international (EUROROAD) + if (app_data>>2)&0x1==1: + self.RDS_data[PI]["AID_list"][AID]["scope"]+="N"#national + if (app_data>>1)&0x1==1: + self.RDS_data[PI]["AID_list"][AID]["scope"]+="R"#regional + if (app_data>>0)&0x1==1: + self.RDS_data[PI]["AID_list"][AID]["scope"]+="U"#urban + #self.RDS_data[PI]["AID_list"][AID]["I"]=(app_data>>3)&0x1#international (EUROROAD) + #self.RDS_data[PI]["AID_list"][AID]["N"]=(app_data>>2)&0x1#national + #self.RDS_data[PI]["AID_list"][AID]["R"]=(app_data>>1)&0x1#regional + #self.RDS_data[PI]["AID_list"][AID]["U"]=(app_data>>0)&0x1#urban + elif variant==1: + self.RDS_data[PI]["AID_list"][AID]["SID"]=(app_data>>6)&0x3f#service identifier + #timing parameters (used to switch away from TMC station without missing messages): + self.RDS_data[PI]["AID_list"][AID]["G"]=(app_data>>12)&0x3#gap parameter + self.RDS_data[PI]["AID_list"][AID]["activity_time"]=(app_data>>4)&0x3 + self.RDS_data[PI]["AID_list"][AID]["window_time"]=(app_data>>2)&0x3 + self.RDS_data[PI]["AID_list"][AID]["delay_time"]=(app_data>>0)&0x3 + elif self.debug: + print("unknown variant %i in TMC 3A group"%variant) + send_pmt = pmt.pmt_to_python.pmt_from_dict({ + "type":"3A_meta", + "data":self.RDS_data[PI]["AID_list"][AID]}) + self.tableobj.message_port_pub(pmt.intern('tmc_raw'), send_pmt) + elif (groupType == "4A"):#CT clock time + bits=BitArray('uint:8=%i,uint:8=%i,uint:8=%i,uint:8=%i,uint:8=%i'%tuple(array[3:8])) + spare,datecode,hours,minutes,offsetdir,local_time_offset = bits.unpack("uint:6,uint:17,uint:5,uint:6,uint:1,uint:5") + local_time_offset*=0.5 + #datecode=((array[3] & 0x03) << 15) | (array[4] <<7)|((array[5] >> 1) & 0x7f)#modified julian date + if datecode==0: + #do not update!! + if self.debug: + print("station:%s sent empty 4A group"%self.RDS_data[PI]["PSN"]) + else: + #hours=((array[5] & 0x1) << 4) | ((array[6] >> 4) & 0x0f) + #minutes=((array[6] &0x0F)<<2)|((array[7] >>6)&0x3) + #offsetdir=(array[7]>>5)&0x1 + #local_time_offset=0.5*((array[7])&0x1F) + if(offsetdir==1): + local_time_offset*=-1 + + date=datetime(1858,11,17)+timedelta(days=int(datecode))#convert from MJD (modified julian date) + + timestring="%02i:%02i (%+.1fh)" % (hours,minutes,local_time_offset) + datestring=date.strftime("%d.%m.%Y") + ctcol=self.colorder.index('time') + self.signals.DataUpdateEvent.emit({'col':ctcol,'row':port,'PI':PI,'string':timestring,'tooltip':datestring}) + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"CT",datestring+" "+timestring+"; datecode(MJD):"+str(datecode)) + self.RDS_data[PI]["time"]["timestring"]=timestring + self.RDS_data[PI]["time"]["datestring"]=datestring + try: + self.RDS_data[PI]["time"]["datetime"]=datetime(date.year,date.month,date.day,hours,minutes)+timedelta(hours=local_time_offset) + except ValueError: + print("ERROR: could not interpret time or date:"+datestring+" "+timestring) + if self.writeDB: + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + elif (groupType == "6A"):#IH inhouse data -> save for analysis + """In House Data: +{'130A': {'1E1077FFFF': {'count': 1, + 'last_time': '2016-12-08 16:26:54.767596'}, + '1F23022015': {'count': 1, + 'last_time': '2016-12-08 16:26:56.341271'}}, + 'D00F': {'1E1023FFFF': {'count': 1, + 'last_time': '2016-12-08 16:26:54.769165'}, + '1F28032008': {'count': 3, + 'last_time': '2016-12-08 16:26:58.272420'}}}""" + ih_data="%02X%02X%02X%02X%02X" %(array[3]&0x1f,array[4],array[5],array[6],array[7]) + if not self.IH_data.has_key(PI): + self.IH_data[PI]={} + if not self.IH_data[PI].has_key(ih_data): + self.IH_data[PI][ih_data]={} + self.IH_data[PI][ih_data]["count"]=0 + self.IH_data[PI][ih_data]["count"]+=1 + self.IH_data[PI][ih_data]["last_time"]=str(datetime.now()) + #TMC-alert-c (grouptype mostly 8A): + elif self.RDS_data[PI]["AID_list"].has_key(52550) and self.RDS_data[PI]["AID_list"][52550]["groupType"]==groupType:#TMC alert-C + tmc_x=array[3]&0x1f #lower 5 bit of block2 + tmc_y=(array[4]<<8)|(array[5]) #block3 + tmc_z=(array[6]<<8)|(array[7])#block4 + send_pmt = pmt.pmt_to_python.pmt_from_dict({ + "type":"alert-c", + "PI":PI, + "PSN":self.RDS_data[PI][PSN], + "TMC_X":tmc_x, + "TMC_Y":tmc_y, + "TMC_Z":tmc_z + }) + self.tableobj.message_port_pub(pmt.intern('tmc_raw'), send_pmt) + tmc_hash=md5.new(str([PI,tmc_x,tmc_y,tmc_z])).hexdigest() + tmc_T=tmc_x>>4 #0:TMC-message 1:tuning info/service provider name + tmc_F=int((tmc_x>>3)&0x1) #identifies the message as a Single Group (F = 1) or Multi Group (F = 0) + Y15=int(tmc_y>>15) + #timestring=self.RDS_data[PI]["time"]["timestring"] + datetime_received=self.RDS_data[PI]["time"]["datetime"] + if tmc_T == 0: + if tmc_F==1:#single group + tmc_msg=tmc_message(PI,tmc_x,tmc_y,tmc_z,datetime_received,self) + self.print_tmc_msg(tmc_msg) + elif tmc_F==0 and Y15==1:#1st group of multigroup + ci=int(tmc_x&0x7) + tmc_msg=tmc_message(PI,tmc_x,tmc_y,tmc_z,datetime_received,self) + #if self.RDS_data[PI]["internals"]["unfinished_TMC"].has_key(ci): + #print("overwriting parital message") + self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]={"msg":tmc_msg,"time":time.time()} + else: + ci=int(tmc_x&0x7) + if self.RDS_data[PI]["internals"]["unfinished_TMC"].has_key(ci): + tmc_msg=self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["msg"] + tmc_msg.add_group(tmc_y,tmc_z) + age=time.time()-self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["time"] + t=(time.time(),PI,age,ci,tmc_msg.is_complete) + #print("%f: continuing message PI:%s,age:%f,ci:%i complete:%i"%t) + self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["time"]=time.time() + if tmc_msg.is_complete: + self.print_tmc_msg(tmc_msg)#print and store message + del self.RDS_data[PI]["internals"]["unfinished_TMC"][tmc_msg.ci]#delete finished message + else: + #if not ci==0: + #print("ci %i not found, discarding"%ci) + pass + + else:#alert plus or provider info + adr=tmc_x&0xf + if 4 <= adr and adr <= 9: + #seen variants 4569, 6 most often + #print("TMC-info variant:%i"%adr) + if adr==4 or adr==5:#service provider name + segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7])) + if self.debug: + print("TMC-info adr:%i (provider name), segment:%s, station:%s"%(adr,segment,self.RDS_data[PI]["PSN"])) + if self.RDS_data[PI]["AID_list"].has_key(52550): + text_list=list(self.RDS_data[PI]["AID_list"][52550]["provider name"]) + seg_adr_start=(adr-4)*4#start of segment + text_list[seg_adr_start:seg_adr_start+4]=segment + self.RDS_data[PI]["AID_list"][52550]["provider name"]="".join(text_list) + if adr== 7:#freq of tuned an mapped station (not seen yet) + freq_TN=tmc_y>>8 + freq_ON=tmc_y&0xff#mapped frequency + if self.debug: + print("TMC-info: TN:%i, station:%s"%(freq_TN,self.RDS_data[PI]["PSN"])) + self.RDS_data[PI]["TMC_TN"]=freq_TN + else: + if self.debug: + print("alert plus on station %s (%s)"%(PI,self.RDS_data[PI]["PSN"]))#(not seen yet) + + #self.tableobj.RDS_data["D301"]["AID_list"][52550]["provider name"]="test____" + #RadioText+ (grouptype mostly 12A): + elif self.RDS_data[PI]["AID_list"].has_key(19415) and self.RDS_data[PI]["AID_list"][19415]["groupType"]==groupType:#RT+ + if not self.RDS_data[PI].has_key("RT+"): + #self.RDS_data[PI]["RT+"]={"history":{},"last_item_toggle_bit":2} + self.RDS_data[PI]["RT+"]={"last_item_toggle_bit":2} + self.RDS_data[PI]["RT+_history"]={} + self.RDS_data[PI]["internals"]["RT+_times"]={} + #self.RDS_data[PI]["RT+"]["last_item_toggle_bit"]=2 + A3_data=self.RDS_data[PI]["AID_list"][19415]["app_data"] + template_number=A3_data&0xff + SCB=(A3_data >> 8)&0x0f#server control bit + CB_flag=(A3_data>>12)&0x1 #is set if template available + rtp_message= ((array[3]&0x1f)<<32)|(array[4]<<24)|(array[5]<<16)|(array[6]<<8)|(array[7]) + item_toggle_bit=(rtp_message>>36)&0x1 + item_running_bit=(rtp_message>>35)&0x1 + tag1=(rtp_message>>17)&(2**18-1)#6+6+6 + tag2=(rtp_message)&(2**17-1)#6+6+5 + tag1_type=self.rtp_classnames[int(tag1>>12)] + tag2_type=self.rtp_classnames[int(tag2>>11)] + tag1_start=int((tag1>>6)&(2**6-1)) + tag1_len=int(tag1&(2**6-1)) + tag2_start=int((tag2>>5)&(2**6-1)) + tag2_len=int(tag2&(2**5-1)) + if not self.RDS_data[PI]["RT+"]["last_item_toggle_bit"] == item_toggle_bit: #new item + #self.RDS_data[PI]["RT+"]["history"][str(datetime.now())]=self.RDS_data[PI]["internals"]["last_rt_tooltip"] + t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT+",str(self.RDS_data[PI]["RT+"])) + if self.writeDB: + db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + self.RDS_data[PI]["RT+_history"][str(datetime.now())]=copy.deepcopy(self.RDS_data[PI]["RT+"])#save old item + self.RDS_data[PI]["RT+"]["last_item_toggle_bit"] = item_toggle_bit + rtcol=self.colorder.index('text') + if self.debug: + print("toggle bit changed on PI:%s, cleared RT-tt"%PI) + self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'tooltip':""}) + if self.RDS_data[PI].has_key("RT_0"): + ab_flag=self.RDS_data[PI]["RT_last_ab_flag"] + rt=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"] + rt_valid=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"] + if not tag1_type=="DUMMY_CLASS" and all(rt_valid[tag1_start:tag1_start+tag1_len+1]): + self.RDS_data[PI]["RT+"][tag1_type]=rt[tag1_start:tag1_start+tag1_len+1] + self.RDS_data[PI]["internals"]["RT+_times"][tag1_type]=time.time() + if not tag2_type=="DUMMY_CLASS" and all(rt_valid[tag2_start:tag2_start+tag2_len+1]): + self.RDS_data[PI]["RT+"][tag2_type]=rt[tag2_start:tag2_start+tag2_len+1] + self.RDS_data[PI]["internals"]["RT+_times"][tag2_type]=time.time() + #check outdated tags: + for tagtype in self.RDS_data[PI]["internals"]["RT+_times"].keys():#.keys() makes copy to avoid RuntimeError: dictionary changed size during iteration + age=time.time()-self.RDS_data[PI]["internals"]["RT+_times"][tagtype] + if age>90:#delete if older than 90 sek#TODO delete if toggle bit changes?, delete if tag changes? (title change -> delete artist) + del self.RDS_data[PI]["internals"]["RT+_times"][tagtype] + del self.RDS_data[PI]["RT+"][tagtype] + + + tags="ir:%i,it:%i"%(item_running_bit,item_toggle_bit) + rtpcol=self.colorder.index('RT+') + self.signals.DataUpdateEvent.emit({'col':rtpcol,'row':port,'PI':PI,'string':tags}) + if(tag2_type=="ITEM.TITLE" and self.RDS_data[PI].has_key("RT_0")):#TODO remove duplicate code + ab_flag=self.RDS_data[PI]["RT_last_ab_flag"] + rt=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"] + rt_valid=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"] + artist="?" + song="?" + if all(rt_valid[tag1_start:tag1_start+tag1_len+1]): + artist=rt[tag1_start:tag1_start+tag1_len+1] + if all(rt_valid[tag2_start:tag2_start+tag2_len+1]): + song=rt[tag2_start:tag2_start+tag2_len+1] + formatted_text="%s by %s"%(song,artist) + rtcol=self.colorder.index('text') + #only update tooltip if text changed -> remove flicker, still flickers :( + if not formatted_text == self.RDS_data[PI]["internals"]["last_rt_tooltip"]: + self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'tooltip':formatted_text}) + self.RDS_data[PI]["internals"]["last_rt_tooltip"] = formatted_text + #elif(not tag1_type=="ITEM.ARTIST" and not tag1_type=="DUMMY_CLASS"): + # print("%s:RT+: tag1_type:%s, tag2_type:%s"%(PI,tag1_type,tag2_type)) + elif (groupType == "14A"):#EON enhanced other networks + #TN = tuned network, ON=other network + if not self.RDS_data[PI].has_key("EON"): + self.RDS_data[PI]["EON"]={} + TP_ON=(array[3]>>4)&0x1 + PI_ON="%02X%02X" %(array[6],array[7]) + variant=array[3]&0xf + self.signals.DataUpdateEvent.emit({'PI':PI_ON,'TP':TP_ON}) + if not self.RDS_data.has_key(PI_ON): + self.init_data_for_PI(PI_ON) + self.RDS_data[PI_ON]["TP"]=TP_ON + self.PI_dict[PI_ON]=0#initialize dict, even if no packets received + if self.log: + print("found station %s via EON on station %s"%(PI_ON,PI)) + if not self.RDS_data[PI]["EON"].has_key(PI_ON): + self.RDS_data[PI]["EON"][PI_ON]={} + self.RDS_data[PI]["EON"][PI_ON]["PSN"]="_"*8 + if variant in range(4):#variant 0..3 -> PS_ON + segment=self.decode_chars(chr(array[4])+chr(array[5])) + name_list=list(self.RDS_data[PI_ON]["PSN"]) + #name_list=list(self.RDS_data[PI]["EON"][PI_ON]["PSN"]) + name_list[variant*2:variant*2+2]=segment + if (name_list[variant*2:variant*2+2]==list(segment)):#segment already there + segmentcolor="purple" + elif(name_list[variant*2:variant*2+2]==['_']*2): #segment new + segmentcolor="purple" + name_list[variant*2:variant*2+2]=segment + else:#name changed (böse) + segmentcolor="red" + name_list=['_']*8 #reset name + name_list[variant*2:variant*2+2]=segment + #reset stored text: + self.RDS_data[PI_ON]["PSN"]="_"*8 + self.RDS_data[PI_ON]["PSN_valid"]=[False]*8 + self.RDS_data[PI_ON]["PSN_valid"][variant*2:variant*2+2]=[True] *2 + PS_ON_str="".join(name_list) + self.RDS_data[PI_ON]["PSN"]=PS_ON_str + self.RDS_data[PI]["EON"][PI_ON]["PSN"]=PS_ON_str + #determine if text is valid + valid=True + for i in range(0,8): + if (not self.RDS_data[PI_ON]["PSN_valid"][i]): + valid = False + if(valid): + #textcolor="black" + textcolor=""#use default color (white if background is black) + + else: + textcolor="gray" + formatted_text=self.color_text(self.RDS_data[PI_ON]["PSN"],variant*2,variant*2+2,textcolor,segmentcolor) + self.RDS_data[PI]["EON"][PI_ON]["PSN"]=PS_ON_str + self.RDS_data[PI_ON]["PSN"]=PS_ON_str + #formatted_text="%s"%("purple",PS_ON_str) + self.signals.DataUpdateEvent.emit({'PI':PI_ON,'PSN':formatted_text}) + try: + t=(PI_ON,self.RDS_data[PI_ON]["PSN"],float(self.RDS_data[PI_ON]["AF"]["main"]),self.RDS_data[PI_ON]["PTY"],int(self.RDS_data[PI_ON]["TP"])) + if self.writeDB: + db.execute("INSERT OR REPLACE INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t) + except KeyError: + #not all info present -> no db update + pass + if variant==4:#AF_ON + if self.debug: + print("AF_ON method A")#TODO + if variant in range(5,10):#variant 5..9 -> mapped freqs + freq_TN=self.decode_AF_freq(array[4]) + freq_ON=self.decode_AF_freq(array[5]) + #lock in tuned network if freq_TN matches decoder frequency + if(self.RDS_data[PI].has_key("tuned_freq") and freq_TN==self.RDS_data[PI]["tuned_freq"]and not self.RDS_data[PI]["AF"].has_key("main")): + if self.log: + print("main frequency found in 14A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq_TN/1e6)) + self.RDS_data[PI]["AF"]["main"]=freq_TN + freq_str="EON_TN:%0.1fM"% (freq_TN/1e6) + self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str}) + #lock in ON if TN is locked in + if(self.RDS_data[PI]["AF"].has_key("main") and self.RDS_data[PI]["AF"]["main"]==freq_TN and not self.RDS_data[PI_ON]["AF"].has_key("main")): + if self.log: + print("mapped frequency found in 14A: station:%s, freq:%0.1fM"% (self.RDS_data[PI_ON]["PSN"],freq_ON/1e6)) + self.RDS_data[PI_ON]["AF"]["main"]=freq_ON + freq_str="EON:%0.1fM"% (freq_ON/1e6) + self.signals.DataUpdateEvent.emit({'PI':PI_ON,'freq':freq_str}) + #print("mapped freq in variant %i:, %i->%i"%(variant,freq_TN,freq_ON)) + if variant==13:#PTY and TA of ON + PTY_ON=array[4]>>3 + TA_ON=array[5]&0x1 + self.RDS_data[PI]["EON"][PI_ON]["TA_ON"]=TA_ON + self.RDS_data[PI]["EON"][PI_ON]["PTY_ON"]=PTY_ON + self.RDS_data[PI_ON]["TA"]=TA_ON + self.RDS_data[PI_ON]["PTY"]=self.pty_dict[PTY_ON] + self.signals.DataUpdateEvent.emit({'PI':PI_ON,'PTY':self.pty_dict[PTY_ON],'TA':TA_ON}) + #rest is reserved + if variant==14:#programme item number of ON + PIN_ON=(array[4]<<8)|(array[5]) + elif (groupType == "8A"): + if self.debug: + print("8A without 3A on PI:%s"%PI) + #else:#other group + #print("group of type %s not decoded on station %s"% (groupType,PI)) + if 1==1: + #printdelay=50 + printdelay=500 + self.printcounter+=0#printing disabled + if self.printcounter == printdelay and self.debug: + + for key in self.RDS_data: + if self.RDS_data[key].has_key("PSN"): + psn=self.RDS_data[key]["PSN"] + else: + psn="?" + print("%s(%s):"%(psn,key),end="") + pp.pprint(self.RDS_data[key]["blockcounts"]) + if self.RDS_data[key].has_key("RT+"): + print("RT+:",end="") + pp.pprint(self.RDS_data[key]["RT+"]) + self.printcounter=0 + + + pr.disable() #disabled-internal-profiling + #end of handle_msg + def print_tmc_msg(self,tmc_msg): + try: + PI=tmc_msg.PI + tmc_F=tmc_msg.is_single + tmc_hash=tmc_msg.tmc_hash + refloc_name="" + reflocs=tmc_msg.location.reflocs + if not self.TMC_data.has_key(tmc_hash):#if message new + try: + self.TMC_data[tmc_hash]=tmc_msg + if self.showTMC: + self.signals.DataUpdateEvent.emit({'TMC_log':tmc_msg,'multi_str':tmc_msg.multi_str()}) + #t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"ALERT-C",message_string.decode("utf-8")) + #self.db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) + timestring=self.RDS_data[PI]["time"]["timestring"] + if self.writeDB: + message_string=tmc_msg.db_string() + t=(tmc_hash,timestring,PI, tmc_F,tmc_msg.event.ecn,int(tmc_msg.location.lcn),tmc_msg.tmc_DP,tmc_msg.tmc_D,tmc_msg.tmc_dir,tmc_msg.tmc_extent,message_string.decode("utf-8"),tmc_msg.multi_str().decode("utf-8"),str(tmc_msg.debug_data)) + self.db.execute("INSERT INTO TMC (hash,time,PI, F,event,location,DP,div,dir,extent,text,multi,rawmgm) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",t) + except Exception as e: + print(e) + raise + #print("line 1064") + + except KeyError: + #print("location '%i' not found"%tmc_location) + pass + def print_results(self): + s = StringIO.StringIO() + sortby = 'cumulative' + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + print(s.getvalue()) + def decode_AF_freq(self,freq_raw): + #if freq_raw in range(1,205):#1..204 BAD coding -> memory usage + if 1<=freq_raw <=204: + return(87500000+freq_raw*100000)#returns int + #return(87.5e6+freq_raw*0.1e6)#returns float + else: + return(0) + def ref_locs(self,loc,name_string): + if(loc==34196):#europe + return(name_string) + else: + try: + locarray=self.lcl_dict[loc] + aref=int(locarray[6]) + loc_name=locarray[4] + return(self.ref_locs(aref,name_string+","+loc_name)) + #return(loc_name) + except KeyError: + return(name_string) + + def decode_chars(self,charstring): + alphabet={ + 0b0010:u" !\#¤%&'()*+,-./", + 0b0011:u"0123456789:;<=>?", + 0b0100:u"@ABCDEFGHIJKLMNO", + 0b0101:u"PQRSTUVWXYZ[\]―_", + 0b0110:u"‖abcdefghijklmno", + 0b0111:u"pqrstuvwxyz{|}¯ ", + 0b1000:u"áàéèíìóòúùÑÇŞßiIJ", + 0b1001:u"âäêëîïôöûüñçşǧıij", + 0b1010:u"ªα©‰Ǧěňőπ€£$←↑→↓", + 0b1011:u"º¹²³±İńűµ¿÷°¼½¾§", + 0b1100:u"ÁÀÉÈÍÌÓÒÚÙŘČŠŽĐĿ", + 0b1101:u"ÂÄÊËÎÏÔÖÛÜřčšžđŀ", + 0b1110:u"ÃÅÆŒŷÝÕØÞŊŔĆŚŹŦð", + 0b1111:u"ãåæœŵýõøþŋŕćśźŧ "}#0xff should not occur (not in standard) (but occured 2017-03-04-9:18 , probably transmission error) + + #charlist=list(charstring) + return_string="" + for i,char in enumerate(charstring): + #split byte + alnr=(ord(char)&0xF0 )>>4 #upper 4 bit + index=ord(char)&0x0F #lower 4 bit + if ord(char)<= 0b00011111:#control code + if ord(char)==0x0D or ord(char)==0x00:#end of message SWR uses: \r\0\0\0 for last block (\0 fill 4 char segment) + #return_string+="\r" + return_string+=char + else: + return_string+="{%02X}"%ord(char)#output control code +# elif ord(char)<= 0b01111111: #real encoding slightly different from ascii +# return_string+=char#use ascii + else: + try: + return_string+=alphabet[alnr][index] + #return_string+=unichr(ord(char))#TODO: properly decide for UTF8 or EBU charset + except KeyError: + return_string+="?%02X?"%ord(char)#symbol not decoded + print("symbol not decoded: "+"?%02X?"%ord(char)+"in string:"+return_string) + pass + return return_string + def color_text(self, text, start,end,textcolor,segmentcolor): + #formatted_text="%s%s%s"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) + #formatted_text="%s%s%s"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) + formatted_text=("%s"*3)% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) + return formatted_text +class rds_parser_table_qt_Widget(QtGui.QWidget): + def __init__(self, signals,label,tableobj,showTMC): + #print("gui initializing")self.tableobj.RDS_data["D3A2"] + self.signals = signals + self.tableobj=tableobj + self.showTMC=showTMC + self.signals.DataUpdateEvent.connect(self.display_data) + """ Creates the QT Range widget """ + QtGui.QWidget.__init__(self) + layout = Qt.QVBoxLayout() + #self.label = Qt.QLabel(label) + #layout.addWidget(self.label)#title of table disabled to save space + #layout.addWidget(self.label) + self.setLayout(layout) + #self.decoder_to_PI={} + self.PI_to_row={} + self.table=QtGui.QTableWidget(self) + rowcount=0 + self.table.setRowCount(rowcount) + self.table.setColumnCount(10) + self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) #disallow editing + + #self.colorder=['ID','freq','name','buttons','PTY','AF','time','text','quality'] + self.colorder=tableobj.colorder + ##button.clicked.connect(self.getDetails) + + layout.addWidget(self.table) + self.table.setHorizontalHeaderLabels(self.colorder) + #self.table.setMaximumHeight(300)#TODO use dynamic value + + button_layout = Qt.QHBoxLayout() + codebutton = QtGui.QPushButton("code.interact") + codebutton.clicked.connect(self.onCLick) + button_layout.addWidget(codebutton) + ih_button = QtGui.QPushButton("show IH data") + ih_button.clicked.connect(self.showIHdata) + button_layout.addWidget(ih_button) + save_button = QtGui.QPushButton("save") + save_button.clicked.connect(self.saveData) + button_layout.addWidget(save_button) + print_button = QtGui.QPushButton("print profile") + print_button.clicked.connect(self.printProfile) + button_layout.addWidget(print_button) + mode_button = QtGui.QPushButton("mode") + mode_button.clicked.connect(self.switchMode) + button_layout.addWidget(mode_button) + layout.addLayout(button_layout) + label_layout = Qt.QHBoxLayout() + self.freq_label=QtGui.QLabel("decoder frequencies:") + #self.freq_label.setTextFormat(QtCore.Qt.RichText) + #self.freq_label.setTextFormat(QtCore.Qt.PlainText) + self.count_label=QtGui.QLabel("count:") + label_layout.addWidget(self.freq_label) + label_layout.addWidget(self.count_label) + layout.addLayout(label_layout) + #TODO set different minsize if TMC is shown + self.setMinimumSize(Qt.QSize(500,40*self.tableobj.nPorts)) + if self.showTMC: + self.tmc_message_label=QtGui.QLabel("TMC messages:") + self.event_filter=QtGui.QLineEdit()#QPlainTextEdit ? + self.location_filter=QtGui.QLineEdit(u"Baden-Württemberg") + #self.location_filter=QtGui.QLineEdit(u"") + self.event_filter.returnPressed.connect(self.filterChanged) + self.location_filter.returnPressed.connect(self.filterChanged) + + filter_layout = Qt.QHBoxLayout() + filter_layout.addWidget(QtGui.QLabel("event filter:")) + filter_layout.addWidget(self.event_filter) + filter_layout.addWidget(QtGui.QLabel("location filter:")) + filter_layout.addWidget(self.location_filter) + + layout.addLayout(filter_layout) + layout.addWidget(self.tmc_message_label) + self.logOutput = Qt.QTextEdit() + self.logOutput.setReadOnly(True) + self.logOutput.setLineWrapMode(Qt.QTextEdit.NoWrap) + self.logOutput.setMaximumHeight(150) + font = self.logOutput.font() + font.setFamily("Courier") + font.setPointSize(10) + layout.addWidget(self.logOutput) + self.lastResizeTime=0 + self.clip = QtGui.QApplication.clipboard() + #self.cb.clear(mode=cb.Clipboard ) + #self.cb.setText("Clipboard Text", mode=cb.Clipboard) + def filterChanged(self): + print("filter changed") + ef=unicode(self.event_filter.text().toUtf8(), encoding="UTF-8").lower() + lf=unicode(self.location_filter.text().toUtf8(), encoding="UTF-8").lower() + self.logOutput.clear() + #filters=[{"type":"location", "str":u"Baden-Württemberg"}] + filters=[{"type":"location", "str":lf},{"type":"event", "str":ef}] + self.logOutput.append(Qt.QString.fromUtf8(self.tableobj.tmc_messages.getLogString(filters))) + #self.logOutput.append(Qt.QString.fromUtf8(self.tableobj.tmc_messages.getLogString([]))) + def keyPressEvent(self, e): + if (e.modifiers() & QtCore.Qt.ControlModifier) and len(self.table.selectedRanges())>0: + selected = self.table.selectedRanges().pop() + selected.leftColumn() + selected.topRow() + if e.key() == QtCore.Qt.Key_C: #copy + try: + qs = self.table.cellWidget(selected.topRow(),selected.leftColumn()).text()#get QString from table + s=re.sub("<.*?>","", str(qs))#remove html tags + self.clip.setText(s) + except Exception as e: + print(e) + print("no text, cant copy") + def insert_empty_row(self): + rowPosition = self.table.rowCount() + self.table.insertRow(rowPosition) + #for col in range(self.table.columnCount()-1):#all labels except in last column -> buttons +# self.table.setCellWidget(rowPosition,col,QtGui.QLabel()) + #initialize labels everywhere: + for col in range(self.table.columnCount()): + self.table.setCellWidget(rowPosition,col,QtGui.QLabel()) + button_layout = Qt.QHBoxLayout() + details_button=QtGui.QPushButton("Detail") + details_button.clicked.connect(functools.partial(self.getDetails, row=rowPosition)) + button_layout.addWidget(details_button) + #2017-03-17 disabled LR buttons + #left_button=QtGui.QPushButton("L") + #left_button.clicked.connect(functools.partial(self.setAudio, row=rowPosition,audio_channel="left")) + #button_layout.addWidget(left_button) + #right_button=QtGui.QPushButton("R") + #right_button.clicked.connect(functools.partial(self.setAudio, row=rowPosition,audio_channel="right")) + #button_layout.addWidget(right_button) + + cellWidget = QtGui.QWidget() + cellWidget.setLayout(button_layout) + button_col=3 + self.table.setCellWidget(rowPosition,button_col,cellWidget) + def display_data(self, event): + #pp.pprint(event) + if type(event)==dict and event.has_key('group_count'): + self.count_label.setText("count:%02i, max:%i"%(event['group_count'],event['group_count_max'])) + if type(event)==dict and event.has_key('decoder_frequencies'): + self.freq_label.setText(event['decoder_frequencies']) + if type(event)==dict and event.has_key('TMC_log') and self.showTMC: + tmc_msg=event['TMC_log'] + ef=unicode(self.event_filter.text().toUtf8(), encoding="UTF-8").lower() + lf=unicode(self.location_filter.text().toUtf8(), encoding="UTF-8").lower() + filters=[{"type":"location", "str":lf},{"type":"event", "str":ef}] + if self.tableobj.tmc_messages.matchFilter(tmc_msg,filters): + self.logOutput.append(Qt.QString.fromUtf8(tmc_msg.log_string())) + self.logOutput.append(Qt.QString.fromUtf8(tmc_msg.multi_str())) + if type(event)==dict and event.has_key('TMC_log_str')and self.showTMC: + ef=unicode(self.event_filter.text().toUtf8(), encoding="UTF-8").lower() + lf=unicode(self.location_filter.text().toUtf8(), encoding="UTF-8").lower() + text=unicode(event['TMC_log_str'], encoding="UTF-8").lower() + if not text.find(lf)==-1 and not text.find(ef)==-1: + self.logOutput.append(Qt.QString.fromUtf8(event['TMC_log_str'])) + if type(event)==dict and event.has_key('PI'): + PI=event['PI'] + if not self.PI_to_row.has_key(PI): + self.PI_to_row[PI]=len(self.PI_to_row)#zero for first PI seen, then count up + self.insert_empty_row() + row=self.PI_to_row[PI] + PIcol=self.colorder.index('ID') + self.table.cellWidget(row,PIcol).setText(PI) + + if event.has_key('freq'): + freqcol=self.colorder.index('freq') + item=self.table.cellWidget(row,freqcol) + item.setText(event['freq']) + if event.has_key('wrong_blocks'): + item=self.table.cellWidget(row,self.colorder.index('quality')) + quality_string="%i%% %s"% (100-2*event['wrong_blocks'],event['dots']) + item.setText(quality_string) + if event.has_key('PTY'): + item=self.table.cellWidget(row,self.colorder.index('PTY')) + tt=item.toolTip() + item.setText(event['PTY']) + item.setToolTip(tt) + if event.has_key('flags'): + item=self.table.cellWidget(row,self.colorder.index('PTY')) + item.setToolTip(Qt.QString(event['flags'])) + if event.has_key('string'): + item=self.table.cellWidget(row,event['col']) + item.setText(event['string']) + if event.has_key('tooltip'): + item=self.table.cellWidget(row,event['col']) + item.setToolTip(Qt.QString(event['tooltip'])) + if event.has_key('AF'): + #setAF + PIcol=self.colorder.index('AF') + self.table.cellWidget(row,PIcol).setText(str(event['AF']['number'])) + if event.has_key('PSN'): + #setPSN + PSNcol=self.colorder.index('name') + item=self.table.cellWidget(row,PSNcol) + item.setText(event['PSN']) + if time.time()-self.lastResizeTime > 2:#every 2 seconds + self.table.resizeColumnsToContents() + self.lastResizeTime=time.time() + #end of display-data + def printProfile(self): + self.tableobj.print_results() + def switchMode(self): + #print("mode switch message sent") + send_pmt = pmt.pmt_to_python.pmt_from_dict({"cmd":"switch mode"}) + #send_pmt = pmt.string_to_symbol("switch mode") + self.tableobj.message_port_pub(pmt.intern('ctrl'), send_pmt) + def saveData(self): + filename="RDS_data_"+str(datetime.now())+".txt" + f=open(self.tableobj.workdir+filename,"w") + rds_data=copy.deepcopy(self.tableobj.RDS_data) + for PI in sorted(rds_data): + try: + del rds_data[PI]['PSN_valid'] + del rds_data[PI]['RT_valid'] + except KeyError: + pass + f.write("Data:%s"%pp.pformat(rds_data)) + f.write("\n\nIn House Data:\n%s"%pp.pformat(self.tableobj.IH_data)) + f.close() + print("data saved in file %s"%filename) + def showIHdata(self): + view=Qt.QDialog() + l=QtGui.QLabel("In House Data:\n%s"%pp.pformat(self.tableobj.IH_data)) + l.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse |QtCore.Qt.TextSelectableByKeyboard) + l.setWordWrap(True) + #self.IH_data + layout=Qt.QVBoxLayout() + layout.addWidget(l) + view.setLayout(layout) + view.exec_() + def setAudio(self,row,audio_channel): + + PIcol=self.colorder.index('ID') + PI=str(self.table.cellWidget(row,PIcol).text()) + freq=int(self.tableobj.RDS_data[PI]['AF']['main']) + #print("setaudio row:%i, chan:%s, PI:%s,freq:%i"%(row,audio_channel,PI,freq)) + send_pmt = pmt.pmt_to_python.pmt_from_dict({"cmd":"set_audio_freq","chan":audio_channel,"freq":freq}) + self.tableobj.message_port_pub(pmt.intern('ctrl'), send_pmt) + #catch: + #print("no freq, cant set decoder")#show notification? popup: too intrusive, log: maybe not visible, other possibility? + #print("freq not in RX BW")#automatically shift freq-tune? + def getDetails(self,row): + PIcol=self.colorder.index('ID') + PI=str(self.table.cellWidget(row,PIcol).text()) + view = chart.DialogViewer() + if self.tableobj.PI_dict.has_key(PI) and self.tableobj.PI_dict[PI]>3:#dont print piechart if no packets received (detected via EON) + table=chart.DataTable() + table.addColumn('groupType') + table.addColumn('numPackets') + blockcounts=copy.deepcopy(self.tableobj.RDS_data[PI]['blockcounts']) + del blockcounts['any'] + #lambda function removes last character of PI string (A or B) and sorts based on integer valure of number in front + for key in sorted(blockcounts,key=lambda elem: int(elem[0:-1])): + count=blockcounts[key] + table.addRow([key+": "+str(count),count]) + mychart=chart.PieChart(table) + view.setGraph(mychart) + #view.resize(360, 240) + #view.resize(380, 550) + rds_data=copy.deepcopy(self.tableobj.RDS_data[PI]) + try: + del rds_data['blockcounts'] + del rds_data['PSN_valid'] + del rds_data["RT_0"]['RT_valid'] + del rds_data["RT_1"]['RT_valid'] + rds_data['internals']['RT_history']=["".join(rt) for rt in rds_data['internals']['RT_history']]#combine char lists into strings (more compact) + except KeyError: + pass + l=QtGui.QLabel("Data:%s"%pp.pformat(rds_data)) + l.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse |QtCore.Qt.TextSelectableByKeyboard) + l.setWordWrap(True) + #l=QtGui.QLabel("Data:") + + #view.layout().addWidget(l) + + scrollArea = QtGui.QScrollArea(self) + scrollArea.setWidgetResizable(True) + scrollArea.setWidget(l) + view.layout().addWidget(scrollArea) + view.setWindowTitle(self.tableobj.RDS_data[PI]["PSN"]) + view.exec_() + def onCLick(self): + print("button clicked") + code.interact(local=locals()) +if __name__ == "__main__": + from PyQt4 import Qt + import sys + + + app = Qt.QApplication(sys.argv) + mainobj= rds_parser_table_qt_Signals() + #mainobj=None + widget = rds_parser_table_qt_Widget(mainobj,"TestLabel") + widget.show() + widget.setWindowTitle("Test Qt gui") + widget.setGeometry(200,200,600,300) + sys.exit(app.exec_()) + + widget = None + + diff --git a/python/tmc_parser.py b/python/tmc_parser.py new file mode 100644 index 0000000..7775d6f --- /dev/null +++ b/python/tmc_parser.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2017 <+YOU OR YOUR COMPANY+>. +# +# This is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import numpy +from gnuradio import gr +import pmt +from PyQt4 import Qt, QtCore, QtGui + +class tmc_parser(gr.sync_block): + """ + docstring for block tmc_parser + """ + def __init__(self, workdir,log,debug,writeDB): + gr.sync_block.__init__(self, + name="tmc_parser", + in_sig=None, + out_sig=None) + self.message_port_register_in(pmt.intern('in')) + self.set_msg_handler(pmt.intern('in'), self.handle_msg) + self.qtwidget=tmc_parser_Widget(self) + def handle_msg(self,msg): + m=pmt.to_python(msg) + self.qtwidget.updateui() + print(m) + def getqtwidget(self): + return self.qtwidget +class tmc_parser_Widget(QtGui.QWidget): + def updateui(self): + print("updating ui") + def filterChanged(self): + print("filter changed") + def __init__(self, parser): + QtGui.QWidget.__init__(self) + layout = Qt.QVBoxLayout() + self.setLayout(layout) + self.parser=parser + self.tmc_message_label=QtGui.QLabel("TMC messages:") + self.event_filter=QtGui.QLineEdit()#QPlainTextEdit ? + self.location_filter=QtGui.QLineEdit(u"Baden-Württemberg") + self.event_filter.returnPressed.connect(self.filterChanged) + self.location_filter.returnPressed.connect(self.filterChanged) + + filter_layout = Qt.QHBoxLayout() + filter_layout.addWidget(QtGui.QLabel("event filter:")) + filter_layout.addWidget(self.event_filter) + filter_layout.addWidget(QtGui.QLabel("location filter:")) + filter_layout.addWidget(self.location_filter) + + layout.addLayout(filter_layout) + layout.addWidget(self.tmc_message_label) + self.logOutput = Qt.QTextEdit() + self.logOutput.setReadOnly(True) + self.logOutput.setLineWrapMode(Qt.QTextEdit.NoWrap) + self.logOutput.setMaximumHeight(150) + font = self.logOutput.font() + font.setFamily("Courier") + font.setPointSize(10) + layout.addWidget(self.logOutput) + self.clip = QtGui.QApplication.clipboard()