#!/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,folium from datetime import datetime import crfa.chart as chart 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 SUFFIXES = {1: 'st', 2: 'nd', 3: 'rd'} def ordinal(num): # I'm checking for 10-20 because those are the digits that # don't follow the normal counting scheme. if 10 <= num % 100 <= 20: suffix = 'th' else: # the second parameter is a default. suffix = SUFFIXES.get(num % 10, 'th') return str(num) + suffix class tmc_event: def __init__(self,ecn,tableobj,tags=[]): self.text_raw="" try: #Code,Text CEN-English,Text (German),Text (German) kein Quantifier,Text (Quantifier = 1),Text (Quantifier >1),N,Q,T,D,U,C,R ,Comment event_array=tableobj.ecl_dict[ecn] self.text_raw=event_array[1] self.is_valid=True except KeyError: print("event '%i' not found"%ecn) self.is_valid=False def __repr__(self): return self.text_raw def __add_tag(self,tag): pass class tmc_location: def __ref_locs(self,lcn,name_string=""): #if not self.is_valid: #not used, since not called from outside # return "" if(lcn==34196):#europe return(name_string) else: try: locarray=self.tableobj.lcl_dict[lcn] 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 __repr__(self): if not self.is_valid: return "invalid lcn:%i"%(self.lcn) #elif self.ltype[0:2] == "P1": #junction elif self.first_name=="":#no first name-> use aref name name=self.aref else: name=self.roadname+","+self.first_name if self.has_koord: return "%s,%i:%s, geo:%s"%(self.ltype,self.subtype,name,self.koord_str) #return '%s,%i:%s, geo:%s'%(self.ltype,self.subtype,name,self.google_maps_link,self.koord_str) else: return "%s,%i:%s"%(self.ltype,self.subtype,name) #if self.ltype[0]=="A":#area #return "%s:%s"%(self.ltype,self.first_name) #elif self.ltype[0]=="L":#line #return "%s:%s"%(self.ltype,self.first_name) #elif self.ltype[0]=="P":#point #return "%s:%s"%(self.ltype,self.first_name) def __init__(self,lcn,tableobj): self.tableobj=tableobj self.reflocs=self.__ref_locs(lcn) self.lcn=lcn self.has_koord=False try: loc_array=tableobj.lcl_dict[lcn] self.ltype=loc_array[0] try: self.subtype=int(loc_array[1]) except ValueError:#should not happen, all rows have int self.subtype=0 print("location subtype %s is invalid in location %i"%(loc_array[1],lcn)) self.roadnumber=loc_array[2] self.roadname=loc_array[3] self.first_name=loc_array[4] self.second_name=loc_array[5] try: #koords stored in WGS84 format with decimal degrees multiplied with 10^5 self.xkoord=int(loc_array[27])/100000.0 self.ykoord=int(loc_array[28])/100000.0 self.koord_str="%f,%f"%(self.ykoord,self.xkoord) #self.koord_str="%f° N, %f° E"%(self.ykoord,self.xkoord) self.google_maps_link="https://www.google.de/maps/place/%f,%f"%(self.ykoord,self.xkoord) self.has_koord=True except ValueError: self.has_koord=False self.is_valid=True if not lcn==34196:#Europe does not have an area reference self.aref=tmc_location(int(loc_array[6]),tableobj) except KeyError: #print("location '%i' not found"%lcn) self.is_valid=False ##LOCATIONCODE;TYPE;SUBTYPE;ROADNUMBER;ROADNAME;FIRST_NAME;SECOND_NAME;AREA_REFERENCE;LINEAR_REFERENCE;NEGATIVE_OFFSET;POSITIVE_OFFSET;URBAN;INTERSECTIONCODE;INTERRUPTS_ROAD;IN_POSITIVE;OUT_POSITIVE;IN_NEGATIVE;OUT_NEGATIVE;PRESENT_POSITIVE;PRESENT_NEGATIVE;EXIT_NUMBER;DIVERSION_POSITIVE;DIVERSION_NEGATIVE;VERÄNDERT;TERN;NETZKNOTEN_NR;NETZKNOTEN2_NR;STATION;X_KOORD;Y_KOORD;POLDIR;ADMIN_County;ACTUALITY;ACTIVATED;TESTED;SPECIAL1;SPECIAL2;SPECIAL3;SPECIAL4;SPECIAL5;SPECIAL6;SPECIAL7;SPECIAL8;SPECIAL9;SPECIAL10 class tmc_message: def __copy__(self):#doesn't copy, tmc_messages dont change if complete return self def __deepcopy__(self,memo):#return self, because deep copy fails return self def __hash__(self):#unused if self.is_single: return self.tmc_hash else: return self.ci def __repr__(self): #event_name=self.ecl_dict[self.tmc_event][1] #message_string="TMC-message,event:%s location:%i,reflocs:%s, station:%s"%(event_name,self.tmc_location,self.ref_locs(self.tmc_location,""),self.RDS_data[PI]["PSN"]) return "single:%i,complete:%i,event:%i location:%s"%(self.is_single,self.is_complete,self.event,self.location) def __init__(self,PI,tmc_x,tmc_y,tmc_z,tableobj):#TODO handle out of sequence data self.debug_data="" self.tableobj=tableobj self.tmc_hash=hash((PI,tmc_x,tmc_y,tmc_z)) tmc_T=tmc_x>>4 #0:TMC-message 1:tuning info/service provider name assert tmc_T==0, "this is tuning info and no alert_c message" Y15=int(tmc_y>>15) self.PI=PI tmc_F=int((tmc_x>>3)&0x1) #identifies the message as a Single Group (F = 1) or Multi Group (F = 0) self.is_single=(tmc_F==1) self.is_multi=(tmc_F==0) if self.is_single or (self.is_multi and Y15==1):#single group or 1st group of multigroup if self.is_single: self.tmc_D=Y15 #diversion bit(Y15) self.tmc_DP=int(tmc_x&0x7) #duration and persistence 3 bits self.is_complete=True else:#1st group of multigroup -> no diversion bit, no duration (sent in mgm_tags) self.is_complete=False self._second_group_received=False self.tmc_D=0 self.tmc_DP=0 self.ci=int(tmc_x&0x7) #continuity index self.data_arr=BitArray() self.mgm_list=[] self.location=tmc_location(tmc_z,tableobj) self.tmc_location=self.location#decrepated self.event=int(tmc_y&0x7ff) #Y10-Y0 self.tmc_event=self.event#decrepated try: self.event_name = self.tableobj.ecl_dict[self.event][1] except KeyError: self.event_name="##Error##" self.tmc_extent=int((tmc_y>>11)&0x7) #3 bits (Y13-Y11) self.tmc_dir=int((tmc_y>>14)&0x1) #+-direction bit (Y14) else:#subsequent groups in multigroup -> Y0..Y11 and Z0..Z15 are special format raise ValueError, "subsequent groups must be added to existing tmc message" def add_group(self,tmc_y,tmc_z): sg=int((tmc_y>>14)&0x1)#=1 if second group Y14 gsi=int((tmc_y>>12)&0x3)#group sequence indicator Y12..13 ,max length:5 if sg==1 and not self._second_group_received:#if second group self.length=gsi self.count=self.length self._second_group_received=True #prevents duplicate second group from resetting counters try: if self.count==gsi: #group in sequence data1=int(tmc_y&0xfff)#data block 1 data2=int(tmc_z)#data block 2 #code.interact(local=locals()) self.data_arr.append("0x%03X"%data1)#3 hex characters self.data_arr.append("0x%04X"%data2)#4 hex characters #print(gsi) #code.interact(local=locals()) if self.count==0:#last group self.is_complete=True self.debug_data=copy.deepcopy(self.data_arr) while len(self.data_arr)>4:#decode mgm label=self.data_arr[0:4].uint del self.data_arr[0:4] fieldlen=mgm_tag.field_lengths[label] data=self.data_arr[0:fieldlen] del self.data_arr[0:fieldlen] if not (label==0 and data.uint ==0):#ignore trailing zeros self.mgm_list.append(mgm_tag(label,data,self.tableobj)) if label==0: self.tmc_DP=data.uint elif label==1 and data.uint==5: self.tmc_D=1#set diversion bit self.count-=1 except AttributeError: #3rd or later group receiver before second #print("out of sequence") pass class mgm_tag:#mgm=multi group message field_lengths=[3, 3, 5, 5, 5, 8, 8, 8, 8, 11, 16, 16, 16, 16, 0, 0] field_names={0:"Duration (value 000 is not allowed)" ,1:"Control code." ,2:"Length of route affected." ,3:"Speed limit advice." ,4:"quantifier (5 bit field)" ,5:"quantifier (8 bit field)" ,6:"Supplementary information code." ,7:"Explicit start time (or time when problem was reported) for driver information only." ,8:"Explicit stop time for driver information and message management." ,9:"Additional event." ,10:"Detailed diversion instructions." ,11:"Destination." ,12:"Reserved for future use" ,13:"Cross linkage to source of problem, on another route." ,14:"Content Separator." ,15:"Reserved for future use."} control_codes={0:"Default urgency increased by one level." ,1: "Default urgency reduced by one level." ,2:" Default directionality of message changed." ,3:" Default 'dynamic' or 'longer-lasting' provision interchanged." ,4:" Default spoken or unspoken duration interchanged." ,5:" Equivalent of diversion bit set to '1'." ,6:" Increase the number of steps in the problem extent by 8." ,7:" Increase the number of steps in the problem extent by 16."} def decode_time_date(self,raw):#label7/8 raw to datestring if raw<=95: hrs=int(raw/4)#takes floor mns=(95%4)*15 return "%i:%i"%(hrs,mns) elif raw<=200:#hour and day return "%i hours"%(raw-96) elif raw<=231:#day of month return "%s of month"%ordinal(raw-200) elif raw<=255:#months return "%s months"%((raw-231)/2.0) else: raise ValueError, "label7/8 time must be between 0 and 255" def length_to_km(self,raw):#label2 raw to km if raw==0: return 100 elif raw <=10: return raw elif raw <=15: return 2*raw-10 elif raw <=31: return 5*raw-55 else: raise ValueError, "label2-length must be between 0 and 31" def __repr__(self): try: if(self.label==0): return "duration: %i"%self.data.uint elif(self.label==1): return "control code: %i"%self.data.uint elif(self.label==2): return "length affected: %i km"%self.length_to_km(self.data.uint) elif(self.label==3): return "speed limit: %i km/h"%(self.data.uint*5) elif(self.label==4): return "5 bit quantifier: %i"%(self.data.uint) elif(self.label==5): return "8 bit quantifier: %i"%(self.data.uint) elif(self.label==6): return "info:%s"%self.tableobj.label6_suppl_info[self.data.uint] elif(self.label==7): return "start: %s"%self.decode_time_date(self.data.uint) elif(self.label==8): return "stop: %s"%self.decode_time_date(self.data.uint) elif(self.label==9): event_string="event: %s"%self.tableobj.ecl_dict[self.data.uint][1] return event_string elif(self.label==10): #location_string="divert via: %s"%",".join(self.tableobj.lcl_dict[self.data.uint][3:5])#roadname(col3) and firstname (col4) location_string="divert via: %s"%tmc_location(self.data.uint,self.tableobj) return location_string elif(self.label==11): location_string="dest.: %s"%tmc_location(self.data.uint,self.tableobj) return location_string elif(self.label==13): location_string="crosslink: %s"%tmc_location(self.data.uint,self.tableobj) return location_string else: return "%i:%s"%(self.label,str(self.data)) except KeyError: return "%i:%s"%(self.label,str(self.data)) def __init__(self,label,data,tableobj): self.tableobj=tableobj assert 0<=label and label <16,"mgm-tag label has to be between 0 and 15" self.label = label self.data = data def label_string(self): return field_names[self.label] 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): """ docstring for block qtguitest """ def goodbye(self): self.clean_data_and_commit_db() print("quitting rds parser table, closing db") #self.db.commit() self.db.close() def __init__(self,signals,nPorts,slot,freq,log,debug,workdir): #QObject.__init__() gr.sync_block.__init__(self, name="RDS Table", in_sig=None, out_sig=None) for i in range(0,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.message_port_register_in(pmt.intern('freq')) self.set_msg_handler(pmt.intern('freq'), self.set_freq) self.log=log self.debug=debug 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.colorder=['ID','freq','name','PTY','AF','time','text','quality','buttons'] self.workdir=workdir self.PI_dict={}#contains PI:numpackets (string:integer) #create new DB file self.db_name=workdir+'RDS_data'+datetime.now().strftime("%Y%m%d_%H%M%S")+'.db' db=sqlite3.connect(self.db_name, check_same_thread=False) #create tables 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() """ tmc_F=(tmc_x>>3)&0x1 #single/multiple group tmc_event=int(tmc_y&0x7ff) #Y10-Y0 tmc_location=tmc_z tmc_DP=tmc_x&0x7 #duration and persistence 3 bits tmc_extent=(tmc_y>>11)&0x7 #3 bits (Y13-Y11) tmc_D=tmc_y>>15 #diversion bit(Y15) tmc_dir=(tmc_y>>14)&0x1 #+-direction bit (Y14)""" self.db=db#TODO fix sqlite atexit.register(self.goodbye) #self.dbc.execute('''CREATE TABLE rtp # (time text,PI text,rtp_string text)''') #workdir="/user/wire2/richter/hackrf_prototypes/" #workdir="/media/clemens/intdaten/uni_bulk/forschungsarbeit/hackrf_prototypes/" 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 #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: #1. LEVEL OF SERVICE Verkehrslage #2. EXPECTED LEVEL OF SERVICE Erwartete Verkehrslage #3. ACCIDENTS Unfälle #4. INCIDENTS Vorfälle #5. CLOSURES AND LANE RESTRICTIONS Straßen- und Fahrbahnsperrungen Straßen- und Fahrbahnsperrungen #6. CARRIAGEWAY RESTRICTIONS Fahrbahnbeschränkungen #7. EXIT RESTRICTIONS Beschränkungen der Ausfahrt #8. ENTRY RESTRICTIONS Beschränkungen der Einfahrt #9. TRAFFIC RESTRICTIONS Verkehrsbeschränkungen #10. CARPOOL INFORMATION Informationen für Fahrgemeinschaften #11. ROADWORKS Bauarbeiten #12. OBSTRUCTION HAZARDS Behinderungen auf der Fahrbahn #13. DANGEROUS SITUATIONS Gefährliche Situationen #14. ROAD CONDITIONS Straßenzustand #15.TEMPERATURES Temperaturen #16. PRECIPITATION AND VISIBILITY Niederschlag und Sichtbehinderungen #17. WIND AND AIR QUALITY Wind und Luftqualität #18. ACTIVITIES Veranstaltungen #19. SECURITY ALERTS Sicherheitsvorfälle #20. DELAYS Zeitverluste #21. CANCELLATIONS Ausfälle #22. TRAVEL TIME INFORMATION Reiseinformationen #23. DANGEROUS VEHICLES Gefährliche Fahrzeuge #24. EXCEPTIONAL LOADS/VEHICLES Außergewöhnliche Ladungen und Fahrzeuge #25. TRAFFIC EQUIPMENT STATUS Störungen an Lichtsignalanlagen und sonstigen Straßenausrüstungen #26. SIZE AND WEIGHT LIMITS Beschränkung der Fahrzeugmaße und -gewichte #27. PARKING RESTRICTIONS Parkregelungen #28. PARKING Parken #29. REFERENCE TO AUDIO BROADCASTS Information #30. SERVICE MESSAGES Service Meldungen #31. SPECIAL MESSAGES Spezielle Meldungen self.ecl_dict=dict((int(rows[0]),rows[1:]) for rows in reader) #read supplementary information code list reader = csv.reader(open(self.workdir+'label6-supplementary-information-codes.csv'), delimiter=',', quotechar='"') reader.next()#skip header self.label6_suppl_info=dict((int(rows[0]),rows[2]) for rows in reader)#code,english,german #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() #with open(workdir+'google_maps_template.html', 'r') as myfile: # self.gmaps_html_template=myfile.read() self.map_markers=[] self.save_data_timer=time.time() self.marker_template="addMarker({{lat: {lat}, lng: {lon}}},'{text}')" self.osm_map = folium.Map(location=[48.7,9.2],zoom_start=10)#centered on stuttgart self.osm_map.save(self.workdir+'osm.html') def clean_data_and_commit_db(self): for PI in self.PI_dict: self.PI_dict[PI]-=1 #print(self.PI_dict) self.db.commit() self.osm_map.save(self.workdir+'osm.html') f=open(self.workdir+'google_maps_markers.js', 'w') markerstring="\n".join(self.map_markers) markerstring+='\n console.log("loaded "+markers.length+" markers")' markerstring+='\n document.getElementById("errorid").innerHTML = "loaded "+markers.length+" markers";' f.write(markerstring) f.close() def set_freq_tune(self,freq): self.tuning_frequency=int(freq) message_string="decoder frequencies:" for num in self.decoder_frequencies: freq=self.decoder_frequencies[num] message_string+="\t %i:%0.1fM"% (num,freq/1e6) message_string+="\t tuned frequency:%0.1fM"%(self.tuning_frequency/1e6) self.signals.DataUpdateEvent.emit({'decoder_frequencies':message_string}) 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 message_string="decoder frequencies:" for num in self.decoder_frequencies: freq=self.decoder_frequencies[num] message_string+="\t %i:%0.1fM"% (num,freq/1e6) message_string+="\t tuned frequency:%0.1fM"%(self.tuning_frequency/1e6) self.signals.DataUpdateEvent.emit({'decoder_frequencies':message_string}) #self.signals.DataUpdateEvent.emit({'row':decoder_num,'freq':freq_str}) #print("nr:%i freq:%s"%(tgtnum,freq_str)) 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: 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"]={} 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":""} def handle_msg(self, msg, port):#port from 0 to 3 if time.time()-self.save_data_timer > 2:#every 10 seconds #TODO lower frequency (high freq for testing) self.save_data_timer=time.time() self.clean_data_and_commit_db() pr.enable() #code.interact(local=locals()) array=pmt.to_python(msg)[1] groupNR=array[2]&0b11110000 groupVar=array[2]&0b00001000 if (groupVar == 0): groupType=str(groupNR >> 4)+"A" else: groupType=str(groupNR >> 4)+"B" 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}) self.RDS_data[PI]["blockcounts"]["any"]+=1 if self.RDS_data[PI]["blockcounts"]["any"]==5: self.RDS_data[PI]["blockcounts"]["any"]=0 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}) #save block to sqlite (commit at end of handle_msg) #(time text,PI text,PSN text, grouptype text,content blob) content="%02X%02X%02X%02X%02X" %(array[3]&0x1f,array[4],array[5],array[6],array[7]) #db=sqlite3.connect(self.db_name) db=self.db #t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],groupType,content) #db.execute("INSERT INTO groups VALUES (?,?,?,?,?)",t) 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) #error 161213: # db.execute("INSERT OR REPLACE INTO grouptypeCounts (PI,grouptype,count) VALUES (?,?,?)",t) #InterfaceError: Error binding parameter 0 - probably unsupported type. #fix?: added str() to PI, but it should already be a string 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) 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 #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 flag_string="TP:%i, TA:%i, MS:%i, DI:%s"%(TP,TA,MS,str(self.RDS_data[PI]["DI"])) self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'flags':flag_string}) #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"): 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"])) 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"])) db.execute("INSERT INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t) 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)") 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" 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"]) db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) t=(self.RDS_data[PI]["PSN"],PI) 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) 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 #print("ECC:%s"%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("language_codes:%s"%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")):#initialize variables self.RDS_data[PI]["RT"]="_"*64 self.RDS_data[PI]["RT_valid"]=[False]*64 self.RDS_data[PI]["RT_all_valid"]=False adr=array[3]&0b00001111 segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7])) #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"]) #determine text length: try: text_end=text_list.index('\r') except ValueError: text_end=64 #assume whole string is important pass 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"]="_"*64 self.RDS_data[PI]["RT_valid"]=[False]*64 self.RDS_data[PI]["RT_valid"][adr*4:adr*4+4]=[True] *4 self.RDS_data[PI]["RT"]="".join(text_list) #determine if (new) text is valid self.RDS_data[PI]["RT_all_valid"]=True for i in range(0,text_end): if (not self.RDS_data[PI]["RT_valid"][i]): self.RDS_data[PI]["RT_all_valid"] = False if(self.RDS_data[PI]["RT_all_valid"]): textcolor="black" l=list(self.RDS_data[PI]["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 t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT",rt) db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t) self.RDS_data[PI]["internals"]["last_valid_rt"]=rt try:#print rt+ if it exist t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT+",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" #formatted_text="%s%s%s"% (textcolor,self.RDS_data[PI]["RT"][:adr*4],segmentcolor,self.RDS_data[PI]["RT"][adr*4:adr*4+4],textcolor,self.RDS_data[PI]["RT"][adr*4+4:]) formatted_text=self.color_text(self.RDS_data[PI]["RT"],adr*4,adr*4+4,textcolor,segmentcolor) #print(self.RDS_data[PI]["RT"]+" valid:"+str(valid)+"valarr:"+str(self.RDS_data[PI]["RT_valid"])) rtcol=self.colorder.index('text') self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'string':formatted_text}) #code.interact(local=locals()) 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 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 variant=app_data>>14 if variant==0: LTN=(app_data>>6)&0x3f#location table number AFI=(app_data>>5)&0x1#alternative frequency indicator M=(app_data>>4)&0x1#transmission mode indicator I=(app_data>>3)&0x1#international (EUROROAD) N=(app_data>>2)&0x1#national R=(app_data>>1)&0x1#regional U=(app_data>>0)&0x1#urban elif variant==1: SID=(app_data>>6)&0x3f#service identifier G=(app_data>>12)&0x3#gap parameter activity_time=(app_data>>4)&0x3 window_time=(app_data>>2)&0x3 delay_time=(app_data>>0)&0x3 elif self.debug: print("unknown variant %i in TMC 3A group"%variant) elif (groupType == "4A"):#CT clock time datecode=((array[3] & 0x03) << 15) | (array[4] <<7)|((array[5] >> 1) & 0x7f) 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 year=int((datecode - 15078.2) / 365.25) month=int((datecode - 14956.1 - int(year * 365.25)) / 30.6001) day=datecode - 14956 - int(year * 365.25) - int(month * 30.6001) if(month == 14 or month == 15):#no idea why -> annex g of RDS spec year += 1; month -= 13 year+=1900 #month was off by one different rounding in c and python? month-=1 #datestring="%02i.%02i.%4i, %02i:%02i (%+.1fh)" % (day,month,year,hours,minutes,local_time_offset) timestring="%02i:%02i (%+.1fh)" % (hours,minutes,local_time_offset) datestring="%02i.%02i.%4i" % (day,month,year) 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) 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) if tmc_T == 0: if tmc_F==1:#single group tmc_msg=tmc_message(PI,tmc_x,tmc_y,tmc_z,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,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== 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"%freq_TN) self.RDS_data[PI]["TMC_TN"]=freq_TN else: a=0 if self.debug: print("alert plus")#(not seen yet) #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+"])) 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"): rt=self.RDS_data[PI]["RT"] rt_valid=self.RDS_data[PI]["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) afcol=self.colorder.index('AF') self.signals.DataUpdateEvent.emit({'col':afcol,'row':port,'PI':PI,'string':tags}) if(tag2_type=="ITEM.TITLE" and self.RDS_data[PI].has_key("RT")):#TODO remove duplicate code rt=self.RDS_data[PI]["RT"] rt_valid=self.RDS_data[PI]["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 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" 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"])) 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: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq_TN/1e6)) self.RDS_data[PI]["AF"]["main"]=freq_TN #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: 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]) #else:#other group if 1==1: #printdelay=50 printdelay=500 self.printcounter+=0#printing disabled if self.printcounter == printdelay and self.debug: #code.interact(local=locals()) 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 #print("group of type %s not decoded on station %s"% (groupType,PI)) #db.commit() #db.close() #pr.disable() #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: if tmc_msg.is_single: #multi_str="single" multi_str="" else: #multi_str="length:%i, list:%s"%(tmc_msg.length,str(tmc_msg.mgm_list)) multi_str="%i:%s"%(tmc_msg.length,str(tmc_msg.mgm_list)) message_string="TMC-message,event:%s lcn:%i,location:%s,reflocs:%s, station:%s"%(tmc_msg.event_name,tmc_msg.location.lcn,tmc_msg.location,reflocs,self.RDS_data[PI]["PSN"]) self.TMC_data[tmc_hash]=tmc_msg self.signals.DataUpdateEvent.emit({'TMC_log':tmc_msg,'multi_str':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) message_string="%s ,locname:%s, reflocs:%s"%(tmc_msg.event_name,tmc_msg.location,reflocs) t=(tmc_hash,str(datetime.now()),PI, tmc_F,tmc_msg.event,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"),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) #self.signals.DataUpdateEvent.emit({'TMC_log_str':multi_str}) if tmc_msg.location.has_koord:#show on map map_tag=tmc_msg.event_name+"; "+multi_str #print to osm map (disabled because slow) #folium.Marker([tmc_msg.location.ykoord,tmc_msg.location.xkoord], popup=map_tag.decode("utf-8")).add_to(self.osm_map) #print to google map marker_string=self.marker_template.format(lat=tmc_msg.location.ykoord,lon=tmc_msg.location.xkoord,text=map_tag,marker_id=len(self.map_markers)) marker_string=self.marker_template.format(lat=tmc_msg.location.ykoord,lon=tmc_msg.location.xkoord,text=map_tag)#without ID self.map_markers.append(marker_string) #code.interact(local=locals()) except Exception as e: print(e) pass #print("line 1064") #code.interact(local=locals()) 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={ 0b1000:u"áàéèíìóòúùÑÇŞßiIJ", 0b1001:u"âäêëîïôöûüñçş??ij", 0b1100:u"ÁÀÉÈÍÌÓÒÚÙŘČŠŽĐĿ", 0b1101:u"ÂÄÊËÎÏÔÖÛÜřčšžđŀ"} charlist=list(charstring) for i,char in enumerate(charstring): #code.interact(local=locals()) if ord(char)<= 0b01111111: charlist[i]=char #use ascii else: #split byte alnr=(ord(char)&0xF0 )>>4 #upper 4 bit index=ord(char)&0x0F #lower 4 bit #code.interact(local=locals()) try: charlist[i]=alphabet[alnr][index] except KeyError: charlist[i]='?'#symbol not decoded #TODO pass return "".join(charlist) def color_text(self, text, start,end,textcolor,segmentcolor): formatted_text="%s%s%s"% (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): #print("gui initializing")self.tableobj.RDS_data["D3A2"] self.signals = signals self.tableobj=tableobj 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) self.setLayout(layout) #self.decoder_to_PI={} self.PI_to_row={} self.table=QtGui.QTableWidget(self) rowcount=2 self.table.setRowCount(rowcount) self.table.setColumnCount(9) self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) #disallow editing #Data empty_text32='________________________________' empty_text64='________________________________________________________________' #empty_text64='\xe4'*64 self.data = {'ID':[ QtGui.QLabel() for i in range(rowcount)], 'freq':[ QtGui.QLabel() for i in range(rowcount)], 'name':[ QtGui.QLabel() for i in range(rowcount)], 'PTY':[ QtGui.QLabel() for i in range(rowcount)], 'AF':[ QtGui.QLabel() for i in range(rowcount)], 'time':[ QtGui.QLabel() for i in range(rowcount)], 'text':[ QtGui.QLabel("_"*64) for i in range(rowcount)], 'quality':[ QtGui.QLabel() for i in range(rowcount)], 'buttons':[]} #Enter data onto Table self.colorder=['ID','freq','name','PTY','AF','time','text','quality','buttons'] horHeaders = [] for n, key in enumerate(self.colorder): #for n, key in enumerate(sorted(self.data.keys())): horHeaders.append(key) for m, item in enumerate(self.data[key]): if type(item)==int:#convert ints to strings newitem = QtGui.QTableWidgetItem(str(item)) self.table.setItem(m, n, newitem) elif isinstance(item,QtGui.QLabel): self.table.setCellWidget(m,n,item) else: newitem = QtGui.QTableWidgetItem(item) self.table.setItem(m, n, newitem) for i in range(rowcount):#create buttons button=QtGui.QPushButton("getDetails") self.table.setCellWidget(i,self.table.columnCount()-1,button) button.clicked.connect(functools.partial(self.getDetails, row=i)) #button.clicked.connect(self.getDetails) #Add Header layout.addWidget(self.label) layout.addWidget(self.table) self.table.setHorizontalHeaderLabels(horHeaders) #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) layout.addLayout(button_layout) self.freq_label=QtGui.QLabel("decoder frequencies:") layout.addWidget(self.freq_label) self.tmc_message_label=QtGui.QLabel("TMC messages:") self.event_filter=QtGui.QLineEdit()#QPlainTextEdit ? self.location_filter=QtGui.QLineEdit(u"Baden-Württemberg") 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) self.lastResizeTime=0 layout.addWidget(self.logOutput) 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()) button=QtGui.QPushButton("getDetails") self.table.setCellWidget(rowPosition,self.table.columnCount()-1,button) button.clicked.connect(functools.partial(self.getDetails, row=rowPosition)) def display_data(self, event): #pp.pprint(event) 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'): 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() reflocs=tmc_msg.location.reflocs reflocs_cmp=unicode(reflocs, encoding="UTF-8").lower() event_cmp=unicode(tmc_msg.event_name, encoding="UTF-8").lower() if not reflocs_cmp.find(lf)==-1 and not event_cmp.find(ef)==-1: message_string="TMC-message,event:%s lcn:%i,location:%s,reflocs:%s, station:%s"%(tmc_msg.event_name,tmc_msg.location.lcn,tmc_msg.location,reflocs,self.tableobj.RDS_data[tmc_msg.PI]["PSN"]) self.logOutput.append(Qt.QString.fromUtf8(message_string)) if event.has_key('multi_str'): self.logOutput.append(Qt.QString.fromUtf8(event['multi_str'])) if type(event)==dict and event.has_key('TMC_log_str'): 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('row'): if type(event)==dict and event.has_key('PI'): #row=event['row'] 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')) item.setText(event['PTY']) 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 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) #code.interact(local=locals()) view.exec_() 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_valid'] del rds_data['RT_valid'] 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) #code.interact(local=locals()) view.exec_() def onCLick(self): print("button clicked") self.tableobj.print_results() #code.interact(local=locals()) #self.logOutput.clear() #self.reset_color() #pp.pprint(event) if __name__ == "__main__": from PyQt4 import Qt import sys # def valueChanged(frequency): # print("Value updated - " + str(frequency)) app = Qt.QApplication(sys.argv) # widget = RangeWidget(Range(0, 100, 10, 1, 100), valueChanged, "Test", "counter_slider", int) 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) #code.interact(local=locals()) sys.exit(app.exec_()) widget = None