You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1956 lines
85 KiB
1956 lines
85 KiB
#!/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 PyQt4 import Qt, QtCore, QtGui |
|
import pprint,code,pickle#for easier testing |
|
pp = pprint.PrettyPrinter() |
|
import cProfile, pstats, StringIO #for profiling |
|
pr = cProfile.Profile()#disabled-internal-profiling |
|
|
|
#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) |
|
|
|
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): |
|
self.tableobj=tableobj |
|
self.ecn=ecn |
|
self.text_raw="##Error##" |
|
self.name="##Error##" |
|
self.length_str=None |
|
self.speed_limit_str=None |
|
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=self.tableobj.ecl_dict[ecn] |
|
self.text_noQ=event_array[2] |
|
if language=="de": |
|
self.text_raw=event_array[1] |
|
self.name=self.text_noQ |
|
else: |
|
self.text_raw=event_array[0]#CEN-english |
|
self.name=re.sub("\(([^()]+)\)","",self.text_raw)#removes everything between parentheses (assume no quantifier is used) |
|
self.text_singleQ=event_array[3] |
|
self.text_pluralQ=event_array[4] |
|
self.nature=event_array[5]#N:nature (blank): information, F:forecast, S:silent |
|
if event_array[0]=="message cancelled": |
|
self.is_cancellation = True |
|
else: |
|
self.is_cancellation = False |
|
self.quantifierType=event_array[6]#Q:quantifier type: (0..12) or blank (no quantifier) |
|
self.durationType=event_array[7]#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 |
|
directionality=event_array[8]#D:directionality: 1:unidirectional, 2:bidirectional, cancellation messages dont have directionality |
|
self.is_unidirectional=True if directionality=="1" else False |
|
self.is_bidirectional=True if directionality=="2" else False |
|
self.urgency=event_array[9]#U:urgency: blank: normal, X:extremely urgent, U:urgent |
|
self.updateClass=int(event_array[10])#C: update class: |
|
self.updateClassName=self.tableobj.tmc_update_class_names[self.updateClass] |
|
self.phrase_code=event_array[11]#R: phrase code |
|
#04789 |
|
#if not self.quantifierType=="" and not self.quantifierType=="0" and not self.quantifierType=="4": |
|
#print("type: %s, C:%s"%(self.quantifierType,self.updateClassName)) |
|
self.is_valid=True |
|
except KeyError: |
|
print("event '%i' not found"%ecn) |
|
self.is_valid=False |
|
def change_directionality(self): |
|
self.is_unidirectional=not self.is_unidirectional |
|
self.is_bidirectional=not self.is_bidirectional |
|
def add_length(self,data):#from label2 |
|
self.length_str="%i km"%mgm_tag.length_to_km(data.uint) |
|
#self.name=self.name.replace("(L)",self.length_str) |
|
def add_speed_limit(self,data):#from label3 |
|
self.speed_limit_str="%i km/h"%(data.uint*5) |
|
def add_quantifier(self,data,bitLength):#from label 4 |
|
self.name=self.text_raw#default |
|
Q_raw=data.uint |
|
if Q_raw==0:#binary zero represents highest value |
|
Q=32 |
|
else: |
|
Q=Q_raw |
|
quantifier_string="type:%s,raw:%i"%(self.quantifierType,Q) |
|
#print(str(self.ecn)+", "+quantifier_string+", "+str(bitLength)+", "+str(data)+", "+self.text_raw) |
|
if self.quantifierType=="": |
|
print("cannot add quantifier %i to event ecn:%i"%(Q_raw,self.ecn)) |
|
elif self.quantifierType=="0":#small numbers |
|
if(Q <= 28): |
|
quantifier_string=str(Q) |
|
else: |
|
quantifier_string=str(30+(Q-29)*2)#30,32,34,36 |
|
#print(quantifier_string) |
|
self.name=self.text_pluralQ.replace("(Q)",quantifier_string) |
|
elif self.quantifierType=="1":#numbers |
|
numbers=[1,2,3,4,10,20,30,40,50,60,70,80,90,100,150,200,250,300,350,400,450,500,550,600,650,700,750,800,850,900,950,1000] |
|
quantifier_string=str(numbers[Q-1]) |
|
elif self.quantifierType=="2":#z.b. für sichtweiten, e.g. for visibility |
|
quantifier_string="%i m"%(Q*10)#TODO meter oder metern? |
|
self.name=self.text_pluralQ.replace("(Q)",quantifier_string) |
|
#quantifier_string="%i Metern"%Q*10 |
|
#self.name=self.text_pluralQ.replace("Q)",quantifier_string+")") |
|
elif self.quantifierType=="4": |
|
speed=Q*5#in kmh |
|
#quantifier_string="von bis zu %i km/h"%speed |
|
quantifier_string="%i km/h"%speed |
|
elif self.quantifierType=="7": |
|
hours=int((Q-1)/6) |
|
minutes=((Q-1)%6)*10 |
|
quantifier_string="%i:%i"%(hours,minutes) |
|
#print(quantifier_string) |
|
elif self.quantifierType=="8": |
|
if Q<=100: |
|
weight=Q*0.1 |
|
else: |
|
weight=10+0.5*(Q-100) |
|
quantifier_string="%it"%weight |
|
self.name=self.text_pluralQ.replace("(Q)",quantifier_string) |
|
elif self.quantifierType=="9": |
|
if Q<=100: |
|
length=Q*0.1 |
|
else: |
|
length=10+0.5*(Q-100) |
|
quantifier_string="%.1fm"%length |
|
self.name=self.text_pluralQ.replace("(Q)",quantifier_string) |
|
else:#other quantifier |
|
self.name=self.text_raw+"; Q(%s)=%s"%(self.quantifierType,quantifier_string) |
|
def __str__(self): |
|
if self.is_valid: |
|
retstr=self.name |
|
if not self.length_str == None: |
|
retstr=self.name.replace("(L)",self.length_str) |
|
if not self.speed_limit_str == None: |
|
if language=="de": |
|
retstr+=" (geschw. begrenzt: %s)"%self.speed_limit_str |
|
else: |
|
retstr+=" (speed limit: %s)"%self.speed_limit_str |
|
return retstr |
|
else: |
|
return("invalid event, ecn:%i"%self.ecn) |
|
def __repr__(self): |
|
return "ecn:%i"%self.ecn |
|
class tmc_location: |
|
#def get_extent_location(self,extent,direction): |
|
#__recursion_get_extent_location(self,extent,direction) |
|
#def __recursion_get_extent_location(self,loc,extent,direction): #direction: 0:pos, 1:neg |
|
def get_extent_location(self,loc,extent,direction): #direction: 0:pos, 1:neg |
|
#print(loc.lcn) |
|
#print(extent) |
|
#print(direction) |
|
if extent==0 or not loc.is_valid: |
|
return loc |
|
else: |
|
offset=loc.positive_offset if direction==0 else loc.negative_offset |
|
if offset=="": |
|
return loc |
|
else: |
|
offset_loc=tmc_location(int(offset),self.tableobj) |
|
#return __recursion_get_extent_location(offset_loc,extent-1,direction) |
|
return offset_loc.get_extent_location(offset_loc,extent-1,direction) |
|
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 __str__(self): |
|
if not self.is_valid: |
|
return "invalid lcn:%i"%(self.lcn) |
|
elif self.ltype=="P1" and self.subtype==1:#autobahnkreuz TODO:only add if name does not already contain "Kreuz" |
|
name="Kreuz "+self.first_name |
|
elif self.ltype=="P1" and self.subtype==2:#autobahndreieck TODO:only add if name does not already contain "Dreieck" |
|
name="Dreieck "+self.first_name |
|
elif not self.roadname=="": |
|
name="STR:"+self.roadname#TODO remove debug |
|
elif not self.first_name=="": |
|
name=self.first_name |
|
else: |
|
name="NO NAME lcn:%i"%(self.lcn) |
|
return "%s,%i:%s"%(self.ltype,self.subtype,name)#no geo |
|
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:<a href="%s">%s</a>'%(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 |
|
self.linRef=None |
|
self.is_valid=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] |
|
if not loc_array[7]=="": |
|
self.linRef=tmc_location(int(loc_array[7]),tableobj) |
|
self.negative_offset=loc_array[8] |
|
self.positive_offset=loc_array[9] |
|
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_google="{lat: %f, lng: %f}"%(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_dict: |
|
"dict of tmc messages sorted by location (LCN) and update class, automatically deletes/updates invalid(ated) items" |
|
marker_template="addMarker({loc},'{text}',{endloc})" |
|
def __init__(self): |
|
self.messages={} |
|
self.message_list=[] |
|
def add(self,message): |
|
self.message_list.append(message) |
|
try: |
|
lcn=message.location.lcn |
|
updateClass=message.event.updateClass |
|
if not self.messages.has_key(lcn): |
|
self.messages[lcn]={} |
|
if message.event.is_cancellation: |
|
try: |
|
self.messages[lcn][updateClass].cancellation_time=message.getTime()#cancellation_time = rx time of cancellation message |
|
except KeyError:#no message to cancel |
|
message.event.name="no message to cancel" |
|
self.messages[lcn][updateClass]=message |
|
else: |
|
self.messages[lcn][updateClass]=message |
|
#print("added message: "+str(message)) |
|
except AttributeError: |
|
print("ERROR, not adding: "+str(message)) |
|
def matchFilter(self,msg,filters): |
|
if not msg.location.is_valid: |
|
return True#always show invalid messages |
|
loc_str=str(msg.location)+str(msg.location.reflocs)+str(msg.location.roadnumber) |
|
|
|
|
|
for f in filters:#filters is list of dicts {"type":"event","str":"Stau"} |
|
stringlist=f["str"].lower().split(";") |
|
for string in stringlist: |
|
if f["type"]=="event" and unicode(str(msg.event), encoding="UTF-8").lower().find(string)==-1:#if event filter does not match |
|
return False |
|
elif f["type"]=="location" and unicode(loc_str, encoding="UTF-8").lower().find(string)==-1:#if location filter does not match |
|
return False |
|
return True |
|
def getLogString(self,filters): |
|
retStr="" |
|
for message in self.message_list: |
|
if self.matchFilter(message,filters): |
|
retStr+=message.log_string() |
|
retStr+="\n" |
|
retStr+=message.multi_str() |
|
retStr+="\n" |
|
return retStr |
|
def getMarkerString(self): |
|
markerstring="" |
|
for lcn in self.messages: |
|
loc=None |
|
endloc=None |
|
map_tag='<p>' |
|
for updateClass in self.messages[lcn]: |
|
message=self.messages[lcn][updateClass] |
|
if message.cancellation_time==None: |
|
color="black" |
|
else: |
|
color="gray" |
|
if message.location.has_koord: |
|
if loc==None:#first message at this location |
|
map_tag+='<h3 style="padding: 0px;margin: 0px;">' |
|
map_tag+=message.location_text() |
|
map_tag+='</h3>' |
|
if message.cancellation_time==None: |
|
endloc=message.end_loc()#line displays length of 1st message (lowest class), that is not cancelled |
|
loc=message.location |
|
map_tag+='<div style="color: %s;">'%color |
|
map_tag+=message.map_string() |
|
map_tag+='<br />' |
|
map_tag+='</div>' |
|
map_tag+='</p>' |
|
if not loc==None: |
|
if endloc==None or not endloc.is_valid: |
|
endloc=loc#creates line of 0 length (dot) |
|
markerstring+=tmc_dict.marker_template.format(loc=loc.koord_str_google,text=map_tag,endloc=endloc.koord_str_google) |
|
markerstring+="\n" |
|
return markerstring |
|
|
|
|
|
class tmc_message: |
|
#Nature Information or Silent Forecast |
|
#Duration Type Dynamic Longer lasting Dynamic Longer lasting |
|
#0 (none) (none) (none) (none) |
|
#1 for at least for at least next within the within the next few |
|
#next 15 min few hours next 15 min hours |
|
#2 for at least for the rest of the within the later today |
|
#next 30 min day next 30 min |
|
#3 for at least until tomorrow within the tomorrow |
|
#next 1 h evening next 1 h |
|
#4 for at least for the rest of the within the the day after tomorrow |
|
#next 2 h week next 2 h |
|
#5 for at least until the end of within the this weekend |
|
#next 3 h next week next 3 h |
|
#6 for at least until the end of within the later this week |
|
#next 4 h the month next 4 h |
|
#7 for the rest of for a long period within the of next week |
|
#the day the day |
|
duration_dict={"Info_dyn":["","for at least next 15 min","for at least next 30 min","for at least next 1 h","for at least next 2 h","for at least next 3 h","for at least next 4 h","for the rest of the day"], |
|
"Info_long":["","for at least next few hours","for the rest of the day","until tomorrow evening","for the rest of the week","until the end of next week","until the end of the month","for a long period"], |
|
"Forecast_dyn":["","within the next 15 min","within the next 30 min","within the next 1 h","within the next 2 h","within the next 3 h","within the next 4 h","within the day"], |
|
"Forecast_long":["","within the next few hours","later today","tomorrow","the day after tomorrow","this weekend","later this week","next week"]} |
|
#Nature Information or Silent Forecast |
|
#Duration Type Dynamic Longer lasting Dynamic Longer lasting |
|
#0 15 min 1h 15 min 1h |
|
#1 15 min 2h 15 min 2h |
|
#2 30 min until midnight 30 min until midnight |
|
#3 1h until midnight 1h until midnight next day |
|
# next day |
|
#4 2h until midnight 2h until midnight next day |
|
# next day |
|
#5 3h until midnight 3h until midnight next day |
|
# next day |
|
#6 4h until midnight 4h until midnight next day |
|
# next day |
|
#7 until midnight until midnight until midnight until midnight next day |
|
# next day |
|
persistence_dict={"dyn":[0.25,0.25,0.5,1,2,3,4,"m"], |
|
"long":[1,2,"m","nm","nm","nm","nm","nm"]} |
|
#m=midnight, nm=midnight nex day, same for forecast and info/silent |
|
#datetime.timedelta(hours=0.25) |
|
def getDuration(self):#returns string |
|
if "D" in self.event.durationType and not self.event.nature=="F": |
|
return tmc_message.duration_dict["Info_dyn"][self.tmc_DP] |
|
elif "L" in self.event.durationType and not self.event.nature=="F": |
|
return tmc_message.duration_dict["Info_long"][self.tmc_DP] |
|
elif "D" in self.event.durationType and self.event.nature=="F": |
|
return tmc_message.duration_dict["Forecast_dyn"][self.tmc_DP] |
|
elif "L" in self.event.durationType and self.event.nature=="F": |
|
return tmc_message.duration_dict["Forecast_long"][self.tmc_DP] |
|
else: |
|
return "" |
|
#self.event.durationType #D,(D),L,(L) |
|
#self.event.nature# "",S,F |
|
def getPersistance(self):#returns timedelta |
|
persistence_dict=tmc_message.persistence_dict |
|
try: |
|
if "D" in self.event.durationType: |
|
return timedelta(hours=persistence_dict["dyn"][self.tmc_DP]) |
|
if "L" in self.event.durationType: |
|
return timedelta(hours=persistence_dict["long"][self.tmc_DP]) |
|
except TypeError: |
|
print("ERROR: TODO line 354") |
|
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 multi_str(self): |
|
if self.is_single: |
|
multi="[single]" |
|
else: |
|
try: |
|
multi="%i:%s"%(self.length,str(self.mgm_list)) |
|
#multi+=";events:" |
|
#for i,event in enumerate(self.events): |
|
#if not i==0: |
|
#multi+=str(event)+"," |
|
except AttributeError: |
|
multi="[multi incomplete]" |
|
if not self.cancellation_time==None: |
|
if language=="de": |
|
multi+=" (aufgehoben um %s)"%self.cancellation_time |
|
else: |
|
multi+=" (cancelled at %s)"%self.cancellation_time |
|
multi+="; "+self.getDuration() |
|
return str(multi) |
|
def events_string(self): |
|
str_list=[str(elem) for elem in self.events] |
|
return str(", ".join(str_list)) |
|
def log_string(self): |
|
return str(self.event.updateClass)+": "+self.getTime()+": "+self.location_text()+": "+self.events_string()+"; "+self.psn |
|
def db_string(self): |
|
return str(self.location)+": "+str(self.event.updateClass)+": "+self.events_string() |
|
def map_string(self): |
|
return str(self.event.updateClass)+": "+self.getTime()+": "+self.events_string()+"; "+self.multi_str()+"; "+self.psn |
|
def end_loc(self): |
|
return self.location.get_extent_location(self.location,self.tmc_extent,self.tmc_dir) |
|
def location_text(self): |
|
text=str(self.location)#use __str__ of location if no location_text implemented |
|
#TODO add "dreieck" for P1.2 -> done in tmc_message.__str__ |
|
if not self.location.linRef==None:#test |
|
#self.tmc_extent and self.tmc_dir are ints |
|
#offset_loc=self.location.get_extent_location(self.location,self.tmc_extent,self.tmc_dir) |
|
offset_loc=self.end_loc() |
|
if offset_loc.is_valid: |
|
#offset_loc_name=str(offset_loc) |
|
offset_loc_name=offset_loc.first_name |
|
else: |
|
print(offset_loc) |
|
offset_loc_name="###INVALID###" |
|
templates={"de_1":"{A}, {B} in Richtung {C}"#codeing handbook: zwischen {D} und {E}, sprachdurchsagen: zwischen {E} und {D} |
|
,"de_2a":", zwischen {D} und {E}" |
|
,"de_2b":", bei {D}"#extent==0 |
|
,"en_1":"{A}, {B} {C}" |
|
,"en_2a":", between {D} and {E}" |
|
,"en_2b":", at {D}"}#extent==0 |
|
text=templates[language+"_1"].format(A=self.location.linRef.roadnumber, B=self.location.linRef.second_name,C=self.location.linRef.first_name) |
|
if self.location.first_name==offset_loc_name:#similar to self.tmc_extent==0 (but some similar location have same same name) |
|
text+=templates[language+"_2b"].format(D=self.location.first_name) |
|
else: |
|
text+=templates[language+"_2a"].format(D=self.location.first_name,E=offset_loc_name) |
|
|
|
#LocCode: RefLine: RoadNr |
|
#A |
|
#LocCode:RefLine:Name2 |
|
#B |
|
#LocCode:RefLine:Name1 |
|
#C |
|
#LocCode:Name1 |
|
#D |
|
#LocCode:Extent:OffsNeg:Name1 |
|
#E |
|
#EventCode: EventText |
|
#F |
|
return str(text) |
|
def __str__(self): |
|
return str(self.event.updateClass)+": "+self.getTime()+": "+self.events_string()+"; "+self.multi_str() |
|
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.ecn,self.location) |
|
def getTime(self):#always returns string |
|
if self.hastime: |
|
return self.datetime_received.strftime("%H:%M") |
|
else: |
|
return "88:88" |
|
def __init__(self,PI,tmc_x,tmc_y,tmc_z,datetime_received,tableobj):#TODO handle out of sequence data |
|
self.psn=tableobj.RDS_data[PI]["PSN"] |
|
#check LTN |
|
try: |
|
msg_ltn=tableobj.RDS_data[PI]["AID_list"][52550]["LTN"] |
|
table_ltn=1#german table |
|
if msg_ltn != table_ltn and tableobj.debug: |
|
print("msg_ltn:%i does not match given table (1) on station: %s"%(msg_ltn,self.psn)) |
|
except KeyError: |
|
if tableobj.debug: |
|
print("no LTN found") |
|
#self.time_received=time_received |
|
self.datetime_received=datetime_received |
|
if self.datetime_received==None: |
|
self.hastime=False |
|
else: |
|
self.hastime=True |
|
self.debug_data="" |
|
self.tableobj=tableobj |
|
self.PI=PI |
|
#self.isCancelled=False |
|
self.cancellation_time=None |
|
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) |
|
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#default to duration of 0, can be changed with MGM |
|
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.event=tmc_event(int(tmc_y&0x7ff),self.tableobj) #Y10-Y0 |
|
self.events=[self.event] |
|
#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) |
|
if not self.event.is_valid: |
|
print("invalid main event") |
|
print(self) |
|
else:#subsequent groups in multigroup -> Y0..Y11 and Z0..Z15 are special format |
|
raise ValueError, "subsequent groups must be added to existing tmc message" |
|
tableobj.tmc_messages.add(self) |
|
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 |
|
|
|
self.data_arr.append("0x%03X"%data1)#3 hex characters |
|
self.data_arr.append("0x%04X"%data2)#4 hex characters |
|
#print(gsi) |
|
|
|
if self.count==0:#last group |
|
self.is_complete=True |
|
self.debug_data=copy.deepcopy(self.data_arr) |
|
last_event=self.event |
|
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:#duration/persistence |
|
self.tmc_DP=data.uint |
|
#label==1: control codes |
|
elif label==1 and data.uint==2: |
|
last_event.change_directionality#change directionality |
|
elif label==1 and data.uint==5: |
|
self.tmc_D=1#set diversion bit |
|
elif label==1 and data.uint==6: |
|
self.tmc_extent+=8#increase extent |
|
elif label==1 and data.uint==7: |
|
self.tmc_extent+=16#increase extent |
|
|
|
elif label==2:#length |
|
last_event.add_length(data) |
|
elif label==3:#speed |
|
last_event.add_speed(data) |
|
elif label==4:#5 bit quantifier |
|
last_event.add_quantifier(data,5) |
|
elif label==5:#8 bit quantifier |
|
last_event.add_quantifier(data,8) |
|
elif label==9:#additional event |
|
last_event=tmc_event(data.uint,self.tableobj) |
|
if not last_event.is_valid: |
|
print("invalid MGM event") |
|
self.events.append(last_event) |
|
|
|
|
|
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."} |
|
@staticmethod |
|
def decode_time_date(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" |
|
@staticmethod |
|
def length_to_km(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=tmc_event(self.data.uint,self.tableobj) |
|
#event_string="event: %s"%self.tableobj.ecl_dict[self.data.uint][1] |
|
#return event_string |
|
return "event: %s"%event.name |
|
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):#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): |
|
#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.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+="<span style='color:green'>  %i:%0.1fM</span>"% (num,freq/1e6) |
|
#print("'color:green'>%i:%0.1fM</span>"% (num,freq/1e6)) |
|
else:#elif self.decoders[num]['synced']==False: |
|
#print("'color:red'>%i:%0.1fM</span>"% (num,freq/1e6)) |
|
message_string+="<span style='color:red'>  %i:%0.1fM</span>"% (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: |
|
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.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 |
|
t=(str(PI),groupType,self.RDS_data[PI]["blockcounts"][groupType])#TODO only update DB every few seconds |
|
if self.writeDB: |
|
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="<span style=''>TP:%i, TA:%i, MS:%i, DI:%s</span>"%(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="<span style='color:%s'>%s</span>"%(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 |
|
#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_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])) |
|
#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 |
|
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) |
|
t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT",rt) |
|
if self.writeDB: |
|
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+",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=self.color_text(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"],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 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: |
|
self.RDS_data[PI]["AID_list"][AID]["LTN"]=(app_data>>6)&0x3f#location table number |
|
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]["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"%(adr,segment)) |
|
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: |
|
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+"])) |
|
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="<font face='Courier New' color='%s'>%s</font>"%("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: 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]) |
|
elif (groupType == "8A"): |
|
if self.debug: |
|
print("8A without 3A on PI:%s"%PI) |
|
#else:#other group |
|
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 |
|
#print("group of type %s not decoded on station %s"% (groupType,PI)) |
|
|
|
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: |
|
#message_string="TMC-message,event:%s lcn:%i,location:%s,reflocs:%s, station:%s"%(str(tmc_msg.event),tmc_msg.location.lcn,tmc_msg.location,reflocs,self.RDS_data[PI]["PSN"]) |
|
#message_string=tmc_msg.log_string() |
|
self.TMC_data[tmc_hash]=tmc_msg |
|
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"] |
|
#message_string="%s ,locname:%s, reflocs:%s"%(str(tmc_msg.event),tmc_msg.location,reflocs) |
|
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)) |
|
if self.writeDB: |
|
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={ |
|
0b1000:u"áàéèíìóòúùÑÇŞßiIJ", |
|
0b1001:u"âäêëîïôöûüñçş??ij", |
|
0b1100:u"ÁÀÉÈÍÌÓÒÚÙŘČŠŽĐĿ", |
|
0b1101:u"ÂÄÊËÎÏÔÖÛÜřčšžđŀ"} |
|
#charlist=list(charstring) |
|
return_string="" |
|
for i,char in enumerate(charstring): |
|
|
|
if ord(char)<= 0b01111111: |
|
#charlist[i]=char #use ascii |
|
return_string+=char |
|
else: |
|
#split byte |
|
alnr=(ord(char)&0xF0 )>>4 #upper 4 bit |
|
index=ord(char)&0x0F #lower 4 bit |
|
try: |
|
#charlist[i]=alphabet[alnr][index] |
|
return_string+=alphabet[alnr][index] |
|
except KeyError: |
|
return_string+="?%02X?"%ord(char) |
|
#charlist[i]='?'#symbol not decoded #TODO |
|
pass |
|
#return "".join(charlist) |
|
return return_string |
|
def color_text(self, text, start,end,textcolor,segmentcolor): |
|
#formatted_text="<font face='Courier New' color='%s'>%s</font><font face='Courier New' color='%s'>%s</font><font face='Courier New' color='%s'>%s</font>"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) |
|
#formatted_text="<span style='background-color: yellow;color:%s'>%s</span><span style='color:%s'>%s</span><span style='color:%s'>%s</span>"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:]) |
|
formatted_text=("<span style='font-family:Courier New;color:%s'>%s</span>"*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,50*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) |
|
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) |
|
#self.table.setCellWidget(rowPosition,self.table.columnCount()-1,button_layout) |
|
cellWidget = QtGui.QWidget() |
|
cellWidget.setLayout(button_layout) |
|
#button_col=self.table.columnCount()-1 |
|
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')) |
|
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 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.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 |
|
|
|
|
|
|