diff --git a/grc/crfa_tmc_parser.xml b/grc/crfa_tmc_parser.xml
index 0993956..0675ea1 100644
--- a/grc/crfa_tmc_parser.xml
+++ b/grc/crfa_tmc_parser.xml
@@ -9,13 +9,21 @@
#if not $label()
#set $label = '"%s"'%$id
#end if
-$(parser) = crfa.tmc_parser($workdir, $log, $debug, $writeDB)
+$(parser) = crfa.tmc_parser($workdir, $log, $debug, $writeDB,maxheight)
$(win) = $(parser).getqtwidget()
$(gui_hint()($win))
+
+ maxheight
+ maxheight
+ 160
+ int
+ part
+
+
work directory
workdir
diff --git a/python/rds_parser_table_qt.py b/python/rds_parser_table_qt.py
index d1a1409..105e323 100644
--- a/python/rds_parser_table_qt.py
+++ b/python/rds_parser_table_qt.py
@@ -26,7 +26,7 @@ import pmt,functools,csv,md5,collections,copy,sqlite3,atexit,time,re,sys
from datetime import datetime
from datetime import timedelta
import crfa.chart as chart
-from crfa.tmc_classes import tmc_dict,tmc_message
+from crfa.tmc_classes import tmc_dict,tmc_message,language
from PyQt4 import Qt, QtCore, QtGui
import pprint,code,pickle#for easier testing
@@ -39,7 +39,7 @@ pr = cProfile.Profile()
from PyQt4.QtCore import QObject, pyqtSignal
from bitstring import BitArray
-language="de"#currently supported: de, en (both partially)
+#language="de"#currently supported: de, en (both partially) #defined in tmc_classes.py
class rds_parser_table_qt_Signals(QObject):
@@ -627,6 +627,7 @@ class rds_parser_table_qt(gr.sync_block):#START
print("unknown variant %i in TMC 3A group"%variant)
send_pmt = pmt.pmt_to_python.pmt_from_dict({
"type":"3A_meta",
+ "PI":PI,
"data":self.RDS_data[PI]["AID_list"][AID]})
self.message_port_pub(pmt.intern('tmc_raw'), send_pmt)
elif (groupType == "4A"):#CT clock time
@@ -684,10 +685,17 @@ class rds_parser_table_qt(gr.sync_block):#START
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
+ datetime_received=self.RDS_data[PI]["time"]["datetime"]
+ psn=self.RDS_data[PI]["PSN"]
+ if datetime_received==None:
+ datetime_str=""
+ else:
+ datetime_str=datetime_received.strftime("%Y-%m-%d %H:%M:%S")
send_pmt = pmt.pmt_to_python.pmt_from_dict({
"type":"alert-c",
"PI":PI,
- "PSN":self.RDS_data[PI]["PSN"],
+ "PSN":psn,
+ "datetime_str":datetime_str,
"TMC_X":tmc_x,
"TMC_Y":tmc_y,
"TMC_Z":tmc_z
@@ -697,14 +705,20 @@ class rds_parser_table_qt(gr.sync_block):#START
tmc_T=tmc_x>>4 #0:TMC-message 1:tuning info/service provider name
tmc_F=int((tmc_x>>3)&0x1) #identifies the message as a Single Group (F = 1) or Multi Group (F = 0)
Y15=int(tmc_y>>15)
- datetime_received=self.RDS_data[PI]["time"]["datetime"]
+ try:
+ ltn=self.RDS_data[PI]["AID_list"][52550]["LTN"]
+ except KeyError:
+ ltn=1#assume germany TODO:add better error handling
+ if self.log:
+ print("no LTN (yet) for PI:%s"%PI)
if tmc_T == 0:
if tmc_F==1:#single group
- tmc_msg=tmc_message(PI,tmc_x,tmc_y,tmc_z,datetime_received,self)
+
+ tmc_msg=tmc_message(PI,psn,ltn,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)
+ tmc_msg=tmc_message(PI,psn,ltn,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()}
@@ -731,7 +745,11 @@ class rds_parser_table_qt(gr.sync_block):#START
#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]))
+ chr1=(tmc_y >> 8) & 0xff
+ chr2=tmc_y & 0xff
+ chr3=(tmc_z >> 8) & 0xff
+ chr4=tmc_z & 0xff
+ segment=self.decode_chars(chr(chr1)+chr(chr2)+chr(chr3)+chr(chr4))
if self.debug:
print("TMC-info adr:%i (provider name), segment:%s, station:%s"%(adr,segment,self.RDS_data[PI]["PSN"]))
if self.RDS_data[PI]["AID_list"].has_key(52550):
diff --git a/python/tmc_classes.py b/python/tmc_classes.py
index 9e3c333..ee18182 100644
--- a/python/tmc_classes.py
+++ b/python/tmc_classes.py
@@ -18,6 +18,16 @@
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
+
+#references to tableobj:
+#~ label6_suppl_info
+#~ ecl_dict
+#~ tmc_update_class_names
+#~ lcl_dict
+#~ debug
+#~ tmc_messages
+
+#rename to common.py?
from bitstring import BitArray
import copy
@@ -496,11 +506,12 @@ class tmc_message:
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"]
+ def __init__(self,PI,psn,ltn,tmc_x,tmc_y,tmc_z,datetime_received,tableobj):#TODO handle out of sequence data
+ #self.psn=tableobj.RDS_data[PI]["PSN"]
+ self.psn=psn
#check LTN
try:
- msg_ltn=tableobj.RDS_data[PI]["AID_list"][52550]["LTN"]
+ msg_ltn=ltn#tableobj.RDS_data[PI]["AID_list"][52550]["LTN"]
table_ltn=1#german table
if msg_ltn != table_ltn and tableobj.debug and False:#disabled, spams log
print("msg_ltn:%i does not match expected table (1) on station: %s"%(msg_ltn,self.psn))
diff --git a/python/tmc_parser.py b/python/tmc_parser.py
index 7775d6f..5a60590 100644
--- a/python/tmc_parser.py
+++ b/python/tmc_parser.py
@@ -23,31 +23,197 @@ import numpy
from gnuradio import gr
import pmt
from PyQt4 import Qt, QtCore, QtGui
+import code,time,csv,sqlite3,atexit
+from bitstring import BitArray
+from crfa.tmc_classes import tmc_dict,tmc_message,language
+from datetime import datetime
+from datetime import timedelta
class tmc_parser(gr.sync_block):
"""
docstring for block tmc_parser
"""
- def __init__(self, workdir,log,debug,writeDB):
+ def __init__(self, workdir,log,debug,writeDB,maxheight):
gr.sync_block.__init__(self,
name="tmc_parser",
in_sig=None,
out_sig=None)
+ self.log=log
+ self.debug=debug
+ self.workdir=workdir
+ atexit.register(self.goodbye)
+ self.qtwidget=tmc_parser_Widget(self,maxheight)
self.message_port_register_in(pmt.intern('in'))
self.set_msg_handler(pmt.intern('in'), self.handle_msg)
- self.qtwidget=tmc_parser_Widget(self)
+ self.tmc_meta={}
+ self.unfinished_messages={}
+ self.TMC_data={}
+ self.tmc_messages=tmc_dict()
+ 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 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)
+ #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 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
+ def goodbye(self):
+ print("closing tmc display")
+ def print_tmc_msg(self,tmc_msg):
+ self.qtwidget.print_tmc_msg(tmc_msg)
+ if self.debug:
+ print("new tmc message %s"%tmc_msg)
+ def initialize_data_for_PI(self,PI):
+ self.unfinished_messages[PI]={}
def handle_msg(self,msg):
m=pmt.to_python(msg)
- self.qtwidget.updateui()
- print(m)
+ PI=m["PI"]
+ if not self.unfinished_messages.has_key(PI):
+ self.initialize_data_for_PI(PI)
+ if m["type"]=="3A_meta":
+ self.tmc_meta[PI]=m["data"]
+ elif m["type"]=="alert-c":
+ #self.qtwidget.updateui()
+ #print(m)
+ psn=m["PSN"]
+ try:
+ ltn=self.tmc_meta[PI]["LTN"]
+ except KeyError:
+ ltn=1#assume germany TODO:add better error handling
+ if self.log:
+ print("no LTN (yet) for PI:%s"%PI)
+ tmc_x=m["TMC_X"]
+ tmc_y=m["TMC_Y"]
+ tmc_z=m["TMC_Z"]
+ if m["datetime_str"]=="":
+ datetime_received=None
+ else:
+ datetime_received=datetime.strptime(m["datetime_str"],"%Y-%m-%d %H:%M:%S")
+ 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,psn,ltn,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,psn,ltn,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.unfinished_messages[PI][ci]={"msg":tmc_msg,"time":time.time()}
+ else:
+ ci=int(tmc_x&0x7)
+ if self.unfinished_messages[PI].has_key(ci):
+ tmc_msg=self.unfinished_messages[PI][ci]["msg"]
+ tmc_msg.add_group(tmc_y,tmc_z)
+ age=time.time()-self.unfinished_messages[PI][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.unfinished_messages[PI]["time"]=time.time()
+ if tmc_msg.is_complete:
+ self.print_tmc_msg(tmc_msg)#print and store message
+ del self.unfinished_messages[PI][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
+ chr1=(tmc_y >> 8) & 0xff
+ chr2=tmc_y & 0xff
+ chr3=(tmc_z >> 8) & 0xff
+ chr4=tmc_z & 0xff
+ segment=self.decode_chars(chr(chr1)+chr(chr2)+chr(chr3)+chr(chr4))
+ if self.log:
+ print("TMC-info adr:%i (provider name), segment:%s, station:%s"%(adr,psn))
+ 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.log:
+ print("TMC-info: TN:%i, station:%s"%(freq_TN,psn))
+ else:
+ if self.log:
+ print("alert plus on station %s (%s)"%(PI,psn))#(not seen yet)
+
def getqtwidget(self):
return self.qtwidget
+ def decode_chars(self,charstring):
+ alphabet={
+ 0b0010:u" !\#¤%&'()*+,-./",
+ 0b0011:u"0123456789:;<=>?",
+ 0b0100:u"@ABCDEFGHIJKLMNO",
+ 0b0101:u"PQRSTUVWXYZ[\]―_",
+ 0b0110:u"‖abcdefghijklmno",
+ 0b0111:u"pqrstuvwxyz{|}¯ ",
+ 0b1000:u"áàéèíìóòúùÑÇŞßiIJ",
+ 0b1001:u"âäêëîïôöûüñçşǧıij",
+ 0b1010:u"ªα©‰Ǧěňőπ€£$←↑→↓",
+ 0b1011:u"º¹²³±İńűµ¿÷°¼½¾§",
+ 0b1100:u"ÁÀÉÈÍÌÓÒÚÙŘČŠŽĐĿ",
+ 0b1101:u"ÂÄÊËÎÏÔÖÛÜřčšžđŀ",
+ 0b1110:u"ÃÅÆŒŷÝÕØÞŊŔĆŚŹŦð",
+ 0b1111:u"ãåæœŵýõøþŋŕćśźŧ "}#0xff should not occur (not in standard) (but occured 2017-03-04-9:18 , probably transmission error)
+
+ #charlist=list(charstring)
+ return_string=""
+ for i,char in enumerate(charstring):
+ #split byte
+ alnr=(ord(char)&0xF0 )>>4 #upper 4 bit
+ index=ord(char)&0x0F #lower 4 bit
+ if ord(char)<= 0b00011111:#control code
+ if ord(char)==0x0D or ord(char)==0x00:#end of message SWR uses: \r\0\0\0 for last block (\0 fill 4 char segment)
+ #return_string+="\r"
+ return_string+=char
+ else:
+ return_string+="{%02X}"%ord(char)#output control code
+# elif ord(char)<= 0b01111111: #real encoding slightly different from ascii
+# return_string+=char#use ascii
+ else:
+ try:
+ return_string+=alphabet[alnr][index]
+ #return_string+=unichr(ord(char))#TODO: properly decide for UTF8 or EBU charset
+ except KeyError:
+ return_string+="?%02X?"%ord(char)#symbol not decoded
+ print("symbol not decoded: "+"?%02X?"%ord(char)+"in string:"+return_string)
+ pass
+ return return_string
class tmc_parser_Widget(QtGui.QWidget):
+ def print_tmc_msg(self,tmc_msg):
+ 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.parser.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()))
def updateui(self):
print("updating ui")
def filterChanged(self):
+ 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":lf},{"type":"event", "str":ef}]
+ self.logOutput.append(Qt.QString.fromUtf8(self.parser.tmc_messages.getLogString(filters)))
print("filter changed")
- def __init__(self, parser):
+ def __init__(self, parser,maxheight):
QtGui.QWidget.__init__(self)
layout = Qt.QVBoxLayout()
self.setLayout(layout)
@@ -69,7 +235,8 @@ class tmc_parser_Widget(QtGui.QWidget):
self.logOutput = Qt.QTextEdit()
self.logOutput.setReadOnly(True)
self.logOutput.setLineWrapMode(Qt.QTextEdit.NoWrap)
- self.logOutput.setMaximumHeight(150)
+ #self.logOutput.setMaximumHeight(150) #label was too wide
+ self.setMaximumHeight(maxheight)
font = self.logOutput.font()
font.setFamily("Courier")
font.setPointSize(10)