From 722ebb180f976cde4cad150160fa3d937b549674 Mon Sep 17 00:00:00 2001
From: csrichter
Date: Sat, 18 Mar 2017 11:44:33 +0100
Subject: [PATCH] home 2017-03-15: added readme
---
COPYING | 340 +++
README.md | 71 +
..._table_qt.sync-conflict-20170315-102705.py | 1986 +++++++++++++++++
3 files changed, 2397 insertions(+)
create mode 100644 COPYING
create mode 100644 README.md
create mode 100644 python/rds_parser_table_qt.sync-conflict-20170315-102705.py
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..9f1e730
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C) 19yy
+
+ This program 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 2 of the License, or
+ (at your option) any later version.
+
+ This program 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 program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..67ab691
--- /dev/null
+++ b/README.md
@@ -0,0 +1,71 @@
+
+### Dependencies
+
+- GNU Radio v3.7.X
+
+- Software from your package manager. For Ubuntu systems, it's
+```
+sudo apt-get install cmake libboost-all-dev liblog4cpp5-dev swig
+```
+- the python module bitstring
+```
+sudo pip install bitstring
+or
+sudo apt-get insall python-bitstring
+```
+
+ubuntu 16.04: sudo apt-get install gnuradio cmake (3.7.9.1-2ubuntu1)
+
+git://git.osmocom.org/rtl-sdr.git
+sudo apt-get install libusb-1.0-0-dev libusb-dev swig
+swig -> "python support" in osmocomsdr
+http://osmocom.org/projects/sdr/wiki/rtl-sdr
+
+apt-get install gr-osmosdr
+### Installation
+
+```
+mkdir build
+cd build
+cmake ..
+make
+sudo make install
+sudo ldconfig
+```
+if you don't see the new blocks in gnuradio companion, click the reload button
+
+#### without root privileges
+put this in your .bashrc file (replace user with your username):
+```
+BASE=/home/user/gnuradio-prefix
+export PATH=${PATH}:${BASE}/bin
+export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${BASE}/lib64:${BASE}/lib
+export PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:${BASE}/lib64/pkgconfig
+export PYTHONPATH=${PYTHONPATH}:${BASE}/lib64/python2.7/site-packages/
+export GRC_BLOCKS_PATH=${GRC_BLOCKS_PATH}:${BASE}/share/gnuradio/grc/blocks
+```
+this tells gnuradio companion where to find the blocks, and your system where to find the executables
+
+when building use the following cmake command (replace user with your username):
+```
+cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/user/gnuradio-prefix ..
+```
+now make should install the module to the specified folder, without needing root privileges
+
+
+
+### Usage
+
+open apps/ifft-RDS-decoder_hier-block.grc flow graph in GNU Radio Companion.
+Click "generate" to create the hierarchical decoder block.
+Click "reload" to load the generated block
+open apps/fft-multi-decoder.grc flow graph.
+set the work directory of the "RDS parser Table" block as the full path (~ shortcut doesnt work) of the data directory (with trailing slash)
+
+### Demos
+
+
+### History
+
+forked from https://github.com/bastibl/gr-rds
+Continuation of gr-rds on BitBucket (originally from Dimitrios Symeonidis https://bitbucket.org/azimout/gr-rds/ and also on CGRAN https://www.cgran.org/wiki/RDS).
diff --git a/python/rds_parser_table_qt.sync-conflict-20170315-102705.py b/python/rds_parser_table_qt.sync-conflict-20170315-102705.py
new file mode 100644
index 0000000..fc90543
--- /dev/null
+++ b/python/rds_parser_table_qt.sync-conflict-20170315-102705.py
@@ -0,0 +1,1986 @@
+#!/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:%s'%(self.ltype,self.subtype,name,self.google_maps_link,self.koord_str)
+ else:
+ return "%s,%i:%s"%(self.ltype,self.subtype,name)
+ #if self.ltype[0]=="A":#area
+ #return "%s:%s"%(self.ltype,self.first_name)
+ #elif self.ltype[0]=="L":#line
+ #return "%s:%s"%(self.ltype,self.first_name)
+ #elif self.ltype[0]=="P":#point
+ #return "%s:%s"%(self.ltype,self.first_name)
+ def __init__(self,lcn,tableobj):
+ self.tableobj=tableobj
+ self.reflocs=self.__ref_locs(lcn)
+ self.lcn=lcn
+ self.has_koord=False
+ 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=''
+ 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+='
'
+ map_tag+=message.location_text()
+ map_tag+='
'
+ 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+=''%color
+ map_tag+=message.map_string()
+ map_tag+='
'
+ map_tag+='
'
+ map_tag+='
'
+ 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))
+ except AttributeError:
+ multi="[multi incomplete]"
+ return str(multi)
+ def info_str(self):
+ info=""
+ info+=self.getDuration()
+ if not self.cancellation_time==None:
+ if language=="de":
+ info+=" (aufgehoben um %s)"%self.cancellation_time
+ else:
+ info+=" (cancelled at %s)"%self.cancellation_time
+ return info
+ 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.info_str()+"; "+self.psn
+ def db_string(self):
+ return str(self.location)+": "+str(self.event.updateClass)+": "+self.events_string()+"; "+self.info_str()
+ def map_string(self):
+ return ''%self.multi_str()+str(self.event.updateClass)+": "+self.getTime()+": "+self.events_string()+self.info_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 and False:#disabled, spams log
+ print("msg_ltn:%i does not match expected 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."}
+ control_codes_short={0:"urgency+=1"
+ ,1:" urgency-=1"
+ ,2:" directionality changed"
+ ,3:" dynamic/longer-lasting changed"
+ ,4:" spoken/unspoken duration changed"
+ ,5:" diversion=1"
+ ,6:" extent+=8"
+ ,7:" extent+=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: %s"%(self.data.uint,mgm_tag.control_codes_short[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+=" %i:%0.1fM"% (num,freq/1e6)
+ #print("'color:green'>%i:%0.1fM"% (num,freq/1e6))
+ else:#elif self.decoders[num]['synced']==False:
+ #print("'color:red'>%i:%0.1fM"% (num,freq/1e6))
+ message_string+=" %i:%0.1fM"% (num,freq/1e6)
+ message_string+=" tuned frequency:%0.1fM"%(self.tuning_frequency/1e6)
+ self.signals.DataUpdateEvent.emit({'decoder_frequencies':message_string})
+ #print(message_string)
+ #self.signals.DataUpdateEvent.emit({'row':decoder_num,'freq':freq_str})
+ #print("nr:%i freq:%s"%(tgtnum,freq_str))
+ def set_freq_tune(self,freq):
+ self.tuning_frequency=int(freq)
+ self.update_freq()
+ def set_freq(self,msg):
+ m = pmt.symbol_to_string(msg)
+ decoder_num=int(m.split()[0])-1#msgs are 1-indexed, decoder_num is 0-indexed
+ freq_str=m.split()[1]
+ try:
+ freq=float(freq_str)
+ self.decoder_frequencies[decoder_num]=freq
+ freq_str="%i:%0.1fM"% (decoder_num,freq/1e6)
+ except ValueError:
+ pass#leave string as is
+ self.update_freq()
+ def init_data_for_PI(self,PI):
+ self.RDS_data[PI]={}
+ #self.RDS_data[PI]["blockcounts"]={}# no defaults (works aswell)
+ #defaults are to keep colors in piechart consistent between stations:
+ self.RDS_data[PI]["blockcounts"]={"0A":0,"1A":0,"2A":0,"3A":0,"4A":0,"6A":0,"8A":0,"12A":0,"14A":0}
+ self.RDS_data[PI]["blockcounts"]["any"]=0
+ self.RDS_data[PI]["AID_list"]={}
+ self.RDS_data[PI]["PSN"]="_"*8
+ self.RDS_data[PI]["PSN_valid"]=[False]*8
+ self.RDS_data[PI]["AF"]={"set":set(),"rxset":set()}
+ self.RDS_data[PI]["TP"]=-1
+ self.RDS_data[PI]["TA"]=-1
+ self.RDS_data[PI]["PTY"]=""
+ self.RDS_data[PI]["DI"]=[2,2,2,2]
+ self.RDS_data[PI]["internals"]={"last_rt_tooltip":"","unfinished_TMC":{},"last_valid_rt":"","last_valid_psn":"","RT_history":[]}
+ self.RDS_data[PI]["time"]={"timestring":"88:88","datestring":"00-00-0000","datetime":None}
+ def handle_msg(self, msg, port):#port from 0 to 3
+ if pmt.to_long(pmt.car(msg))==1L:
+ data=pmt.to_python(pmt.cdr(msg))
+ #print("port:%i, data: %s"%(port,data))
+ self.decoders[port]['synced']=data
+ self.update_freq()
+ else: #elif pmt.to_long(pmt.car(msg))==0L
+ array=pmt.to_python(msg)[1]
+
+ if time.time()-self.save_data_timer > 10:#every 10 seconds
+ self.save_data_timer=time.time()
+ self.clean_data_and_commit_db()
+
+ if time.time()-self.minute_count_timer > 3:#every 3 second
+ self.minute_count_max=self.minute_count
+ self.signals.DataUpdateEvent.emit({'group_count':self.minute_count,'group_count_max':self.minute_count_max})
+ self.minute_count=0
+ self.minute_count_timer=time.time()
+ #pr.enable()#disabled-internal-profiling
+ self.minute_count+=1
+ #self.signals.DataUpdateEvent.emit({'group_count':self.minute_count,'group_count_max':self.minute_count_max})
+ if self.writeDB:
+ #db=sqlite3.connect(self.db_name)
+ db=self.db
+
+
+ groupNR=array[2]&0b11110000
+ groupVar=array[2]&0b00001000
+ if (groupVar == 0):
+ groupType=str(groupNR >> 4)+"A"
+ else:
+ groupType=str(groupNR >> 4)+"B"
+ #if self.debug:
+ #PI=str(port)+"_%02X%02X" %(array[0],array[1])
+ #else:
+ #PI="%02X%02X" %(array[0],array[1])
+ PI="%02X%02X" %(array[0],array[1])
+ TP=(array[2]>>2)&0x1
+ block2=(array[2]<<8)|(array[3]) #block2
+ PTY=(block2>>5)&0x1F
+ wrong_blocks=int(array[12])
+
+ try:
+ self.PI_dict[PI]+=1
+ except KeyError:
+ pass
+
+ if not self.RDS_data.has_key(PI):#station invalid/new
+ if not self.PI_dict.has_key(PI):#1st group
+ self.PI_dict[PI]=1
+ return#dont decode further if not yet valid
+ elif self.PI_dict[PI]>2:#count station as valid if more than 2 packets received
+ self.init_data_for_PI(PI)#initialize dict for station
+ if self.log:
+ print("found station %s"%PI)
+ else:
+ return#dont decode further if not yet valid
+
+ if self.decoder_frequencies.has_key(port):
+ freq=self.decoder_frequencies[port]
+ freq_str="%i:%0.1fM"% (port,freq/1e6)
+ self.RDS_data[PI]["tuned_freq"]=freq
+ #self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str})
+ if self.RDS_data[PI]["blockcounts"].has_key(groupType):
+ self.RDS_data[PI]["blockcounts"][groupType] +=1 #increment
+ else:
+ self.RDS_data[PI]["blockcounts"][groupType] = 1 #initialize (1st group of this type)
+ self.RDS_data[PI]["blockcounts"]["any"]+=1
+ if self.RDS_data[PI]["blockcounts"]["any"]==5:
+ self.RDS_data[PI]["blockcounts"]["any"]=0
+ 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="TP:%i, TA:%i, MS:%i, DI:%s"%(TP,TA,MS,str(self.RDS_data[PI]["DI"]))
+ pty_colored=self.RDS_data[PI]["PTY"]
+ if TP==1:
+ if TA==1:
+ color="red"
+ elif TA==0:
+ color="green"
+ else:
+ color="yellow"
+ pty_colored="%s"%(color,self.RDS_data[PI]["PTY"])
+
+ self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'flags':flag_string,'PTY':pty_colored})
+
+ #224 1110 0000 = no AF
+ #225 1110 0001 = 1AF
+ #249 1111 1001 = 25AF
+ fillercode=205#1100 1101
+ if not self.RDS_data[PI]["AF"].has_key("main") and self.RDS_data[PI].has_key("tuned_freq"):
+ #if self.RDS_data[PI].has_key("tuned_freq"):#update main freq even if one exists -> DB problem
+ freq=self.decode_AF_freq(array[4])
+ if freq==self.RDS_data[PI]["tuned_freq"]:
+ self.RDS_data[PI]["AF"]["main"]=freq
+ if self.log:
+ print("main frequency found in 0A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq/1e6))
+ freq_str="0A:%0.1fM"% (freq/1e6)
+ self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str})
+ t=(PI,self.RDS_data[PI]["PSN"],float(freq),self.RDS_data[PI]["PTY"],int(self.RDS_data[PI]["TP"]))
+ if self.writeDB:
+ db.execute("INSERT INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t)
+ freq=self.decode_AF_freq(array[5])
+ if freq==self.RDS_data[PI]["tuned_freq"]:
+ self.RDS_data[PI]["AF"]["main"]=freq
+ if self.log:
+ print("main frequency found in 0A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq/1e6))
+ freq_str="0A:%0.1fM"% (freq/1e6)
+ self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str})
+ t=(PI,self.RDS_data[PI]["PSN"],float(freq),self.RDS_data[PI]["PTY"],int(self.RDS_data[PI]["TP"]))
+ if self.writeDB:
+ db.execute("INSERT INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t)
+ if self.RDS_data[PI].has_key("tuned_freq") :#TODO add secondary freqs
+ freq=self.decode_AF_freq(array[4])
+ diff=abs(freq-self.RDS_data[PI]["tuned_freq"])
+ if diff<100000:
+ self.RDS_data[PI]["AF"]["rxset"].add(freq)
+ freq=self.decode_AF_freq(array[5])
+ diff=abs(freq-self.RDS_data[PI]["tuned_freq"])
+ if diff<100000:
+ self.RDS_data[PI]["AF"]["rxset"].add(freq)
+
+ if(array[4]>= 224 and array[4]<= 249):
+ #print("AF1 detected")
+ self.RDS_data[PI]["AF"]['number']=array[4]-224
+ #self.RDS_data[PI]["AF"]['main']=self.decode_AF_freq(array[5])
+ self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'AF':self.RDS_data[PI]["AF"]})
+ if(array[5]>= 224 and array[5]<= 249):
+ print("AF2 detected (shouldn't happen) %s"%array[5])
+
+
+ #add frequencies to set
+ self.RDS_data[PI]["AF"]["set"].add(self.decode_AF_freq(array[4]))
+ self.RDS_data[PI]["AF"]["set"].add(self.decode_AF_freq(array[5]))
+ try:
+ self.RDS_data[PI]["AF"]["set"].remove(0)#remove control characters
+ except KeyError:
+ pass
+
+ name_list=list(self.RDS_data[PI]["PSN"])
+ if (name_list[adr*2:adr*2+2]==list(segment)):#segment already there
+ segmentcolor="green"
+ elif(name_list[adr*2:adr*2+2]==['_']*2): #segment new
+ segmentcolor="orange"
+ name_list[adr*2:adr*2+2]=segment
+ else:#name changed (böse)
+ segmentcolor="red"
+ name_list=['_']*8 #reset name
+ name_list[adr*2:adr*2+2]=segment
+ #reset stored text:
+ self.RDS_data[PI]["PSN"]="_"*8
+ self.RDS_data[PI]["PSN_valid"]=[False]*8
+ self.RDS_data[PI]["PSN_valid"][adr*2:adr*2+2]=[True] *2
+ self.RDS_data[PI]["PSN"]="".join(name_list)
+ #determine if text is valid
+ valid=True
+ for i in range(0,8):
+ if (not self.RDS_data[PI]["PSN_valid"][i]):
+ valid = False
+ if(valid):
+ #textcolor="black"
+ textcolor=""#use default color (white if background is black)
+ if not self.RDS_data[PI]["internals"]["last_valid_psn"]==self.RDS_data[PI]["PSN"]:#ignore duplicates
+ t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"PSN_valid",self.RDS_data[PI]["PSN"])
+ if self.writeDB:
+ db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t)
+ t=(self.RDS_data[PI]["PSN"],PI)
+ if self.writeDB:
+ db.execute("UPDATE OR IGNORE stations SET PSN=? WHERE PI IS ?",t)
+ self.RDS_data[PI]["internals"]["last_valid_psn"]=self.RDS_data[PI]["PSN"]
+ else:
+ textcolor="gray"
+ formatted_text=self.color_text(self.RDS_data[PI]["PSN"],adr*2,adr*2+2,textcolor,segmentcolor)
+ self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'PSN':formatted_text})
+ elif (groupType == "1A"):#PIN programme item number
+ #wer nutzt 1A gruppen?
+ #antenne1: variants: 0(ECC),
+ PIN=(array[6]<<8)|(array[7])
+ SLC=(array[4]<<8)|(array[5])&0xfff#slow labeling code
+ radio_paging=array[3]&0x1f
+ LA=array[4]>>7#linkage actuator
+ variant=(array[4]>>4)&0x7
+ PIN_day=(PIN>>11)&0x1f
+ PIN_hour=(PIN>>6)&0x1f
+ PIN_minute=PIN&0x3f
+ PIN_valid= PIN_day in range(1,32) and PIN_hour in range(0,24) and PIN_minute in range(0,60)
+ if PIN_valid:
+ self.RDS_data[PI]["PIN"]=[PIN_day,PIN_hour,PIN_minute]
+ data_string="variant:%i,SLC:%04X,PIN (valid):%s "%(variant,SLC,str([PIN_day,PIN_hour,PIN_minute]))
+ else:
+ data_string="variant:%i,SLC:%04X,PIN:%04X "%(variant,SLC,PIN)
+ #%02X%02X%02X%02X%02X
+
+ t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"PIN",data_string)
+ if self.writeDB:
+ db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t)
+ if self.debug and not variant==0:#print if not seen before
+ print("PI:%s PSN:%s uses variant %i of 1A"%(PI,self.RDS_data[PI]["PSN"],variant))
+ if variant==0:
+ paging=array[4]&0xf
+ extended_country_code=array[5]
+ self.RDS_data[PI]["ECC"]=extended_country_code
+ if self.debug:
+ print("PI:%s PSN:%s,ECC:%s"%(PI,self.RDS_data[PI]["PSN"],hex(extended_country_code)))
+ elif variant==1:
+ TMC_info=SLC
+ elif variant==2:
+ paging_info=SLC
+ elif variant==3:
+ language_codes=SLC
+ if self.debug:
+ print("PI:%s PSN:%s,language_codes:%s"%(PI,self.RDS_data[PI]["PSN"],hex(language_codes)))
+ elif variant==6:
+ #for use by broadcasters
+ if self.debug:
+ print("PI:%s PSN:%s uses variant 6 of 1A"%(PI,self.RDS_data[PI]["PSN"]))
+ elif variant==7:
+ ESW_channel_identification=SLC
+ #end of 1A decode
+ elif (groupType == "2A"):#RT radiotext
+ if(not self.RDS_data[PI].has_key("RT_0")):#initialize variables
+ self.RDS_data[PI]["RT_0"]={"RT":"_"*64,"RT_valid":[False]*64,"RT_all_valid":False}
+ self.RDS_data[PI]["RT_1"]={"RT":"_"*64,"RT_valid":[False]*64,"RT_all_valid":False}
+ #self.RDS_data[PI]["RT"]="_"*64
+ #self.RDS_data[PI]["RT_valid"]=[False]*64
+ #self.RDS_data[PI]["RT_all_valid"]=False
+ self.RDS_data[PI]["RT_last_ab_flag"]=2
+
+ adr= array[3]&0b00001111
+ ab_flag=(array[3]&0b00010000)>>4
+ #print("PI:%s, AB:%i"%(PI,ab_flag))
+
+
+ #if self.RDS_data[PI]["RT_last_ab_flag"] !=ab_flag:#AB flag changed -> clear text
+ # self.RDS_data[PI]["RT"]="_"*64
+ # self.RDS_data[PI]["RT_valid"]=[False]*64
+ # self.RDS_data[PI]["RT_last_ab_flag"] =ab_flag
+ self.RDS_data[PI]["RT_last_ab_flag"] =ab_flag
+
+ segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7]))
+ #print("RT:adress: %d, segment:%s"%(adr,segment))
+ #self.signals.DataUpdateEvent.emit({'col':5,'row':port,'PI':PI,'groupType':groupType,'adress':adr,'segment':segment})
+ text_list=list(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"])
+ #determine text length:
+ try:
+ text_end=text_list.index('\r')
+ except ValueError:
+ text_end=64 #assume whole string is important
+ pass
+ predicted=False
+ if (text_list[adr*4:adr*4+4]==list(segment)):#segment already there
+ segmentcolor="green"
+ elif (text_list[adr*4:adr*4+4]==['_']*4):#segment new
+ segmentcolor="orange"
+ text_list[adr*4:adr*4+4]=segment
+ else:
+ segmentcolor="red"
+ text_list=['_']*64 #clear text
+ text_list[adr*4:adr*4+4]=segment
+ #reset stored text:
+ self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="_"*64
+ self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"]=[False]*64
+ #predict RT from last texts:
+ for rt in self.RDS_data[PI]["internals"]["RT_history"]:
+ if rt[adr*4:adr*4+4]==list(segment):
+ self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="".join(rt)
+ predicted=True
+
+ self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"][adr*4:adr*4+4]=[True] *4
+ if not predicted:
+ self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]="".join(text_list)
+
+ #determine if (new) text is valid
+ self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"]=True
+ for i in range(0,text_end):
+ if (not self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"][i]):
+ self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"] = False
+ if(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_all_valid"]):
+ #textcolor="black"
+ textcolor=""#use default color (white if background is black)
+ l=list(self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"])
+ rt="".join(l[0:text_end])#remove underscores(default symbol) after line end marker
+ if not self.RDS_data[PI]["internals"]["last_valid_rt"]==rt:#ignore duplicates #TODO add 2nd order duplicates ABAB
+ self.RDS_data[PI]["internals"]["RT_history"].append(l)
+ if len(self.RDS_data[PI]["internals"]["RT_history"])>10:#only store last 10 RTs
+ self.RDS_data[PI]["internals"]["RT_history"].pop(0)
+ 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 AID==52550:#TMC alert-c initialize
+ self.RDS_data[PI]["AID_list"][AID]["provider name"]="________"
+ if self.log:
+ print("new ODA: AID:%i, name:'%s', app_group:%s, station:%s" %(AID,app_name,app_group,PI))
+ #decode 3A group of TMC
+ if AID==52550:#TMC alert-c (continuously update)
+ variant=app_data>>14
+ if variant==0:
+ self.RDS_data[PI]["AID_list"][AID]["LTN"]=(app_data>>6)&0x3f#location table number (6 bits)
+ self.RDS_data[PI]["AID_list"][AID]["AFI"]=(app_data>>5)&0x1#alternative frequency indicator
+ self.RDS_data[PI]["AID_list"][AID]["M"]=(app_data>>4)&0x1#transmission mode indicator
+ #Message Geographical Scope:
+ self.RDS_data[PI]["AID_list"][AID]["scope"]=""
+ if (app_data>>3)&0x1==1:
+ self.RDS_data[PI]["AID_list"][AID]["scope"]+="I"#international (EUROROAD)
+ if (app_data>>2)&0x1==1:
+ self.RDS_data[PI]["AID_list"][AID]["scope"]+="N"#national
+ if (app_data>>1)&0x1==1:
+ self.RDS_data[PI]["AID_list"][AID]["scope"]+="R"#regional
+ if (app_data>>0)&0x1==1:
+ self.RDS_data[PI]["AID_list"][AID]["scope"]+="U"#urban
+ #self.RDS_data[PI]["AID_list"][AID]["I"]=(app_data>>3)&0x1#international (EUROROAD)
+ #self.RDS_data[PI]["AID_list"][AID]["N"]=(app_data>>2)&0x1#national
+ #self.RDS_data[PI]["AID_list"][AID]["R"]=(app_data>>1)&0x1#regional
+ #self.RDS_data[PI]["AID_list"][AID]["U"]=(app_data>>0)&0x1#urban
+ elif variant==1:
+ self.RDS_data[PI]["AID_list"][AID]["SID"]=(app_data>>6)&0x3f#service identifier
+ #timing parameters (used to switch away from TMC station without missing messages):
+ self.RDS_data[PI]["AID_list"][AID]["G"]=(app_data>>12)&0x3#gap parameter
+ self.RDS_data[PI]["AID_list"][AID]["activity_time"]=(app_data>>4)&0x3
+ self.RDS_data[PI]["AID_list"][AID]["window_time"]=(app_data>>2)&0x3
+ self.RDS_data[PI]["AID_list"][AID]["delay_time"]=(app_data>>0)&0x3
+ elif self.debug:
+ print("unknown variant %i in TMC 3A group"%variant)
+ elif (groupType == "4A"):#CT clock time
+ bits=BitArray('uint:8=%i,uint:8=%i,uint:8=%i,uint:8=%i,uint:8=%i'%tuple(array[3:8]))
+ spare,datecode,hours,minutes,offsetdir,local_time_offset = bits.unpack("uint:6,uint:17,uint:5,uint:6,uint:1,uint:5")
+ local_time_offset*=0.5
+ #datecode=((array[3] & 0x03) << 15) | (array[4] <<7)|((array[5] >> 1) & 0x7f)#modified julian date
+ if datecode==0:
+ #do not update!!
+ if self.debug:
+ print("station:%s sent empty 4A group"%self.RDS_data[PI]["PSN"])
+ else:
+ #hours=((array[5] & 0x1) << 4) | ((array[6] >> 4) & 0x0f)
+ #minutes=((array[6] &0x0F)<<2)|((array[7] >>6)&0x3)
+ #offsetdir=(array[7]>>5)&0x1
+ #local_time_offset=0.5*((array[7])&0x1F)
+ if(offsetdir==1):
+ local_time_offset*=-1
+
+ date=datetime(1858,11,17)+timedelta(days=int(datecode))#convert from MJD (modified julian date)
+
+ timestring="%02i:%02i (%+.1fh)" % (hours,minutes,local_time_offset)
+ datestring=date.strftime("%d.%m.%Y")
+ ctcol=self.colorder.index('time')
+ self.signals.DataUpdateEvent.emit({'col':ctcol,'row':port,'PI':PI,'string':timestring,'tooltip':datestring})
+ t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"CT",datestring+" "+timestring+"; datecode(MJD):"+str(datecode))
+ self.RDS_data[PI]["time"]["timestring"]=timestring
+ self.RDS_data[PI]["time"]["datestring"]=datestring
+ try:
+ self.RDS_data[PI]["time"]["datetime"]=datetime(date.year,date.month,date.day,hours,minutes)+timedelta(hours=local_time_offset)
+ except ValueError:
+ print("ERROR: could not interpret time or date:"+datestring+" "+timestring)
+ if self.writeDB:
+ db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t)
+ elif (groupType == "6A"):#IH inhouse data -> save for analysis
+ """In House Data:
+{'130A': {'1E1077FFFF': {'count': 1,
+ 'last_time': '2016-12-08 16:26:54.767596'},
+ '1F23022015': {'count': 1,
+ 'last_time': '2016-12-08 16:26:56.341271'}},
+ 'D00F': {'1E1023FFFF': {'count': 1,
+ 'last_time': '2016-12-08 16:26:54.769165'},
+ '1F28032008': {'count': 3,
+ 'last_time': '2016-12-08 16:26:58.272420'}}}"""
+ ih_data="%02X%02X%02X%02X%02X" %(array[3]&0x1f,array[4],array[5],array[6],array[7])
+ if not self.IH_data.has_key(PI):
+ self.IH_data[PI]={}
+ if not self.IH_data[PI].has_key(ih_data):
+ self.IH_data[PI][ih_data]={}
+ self.IH_data[PI][ih_data]["count"]=0
+ self.IH_data[PI][ih_data]["count"]+=1
+ self.IH_data[PI][ih_data]["last_time"]=str(datetime.now())
+ #TMC-alert-c (grouptype mostly 8A):
+ elif self.RDS_data[PI]["AID_list"].has_key(52550) and self.RDS_data[PI]["AID_list"][52550]["groupType"]==groupType:#TMC alert-C
+ tmc_x=array[3]&0x1f #lower 5 bit of block2
+ tmc_y=(array[4]<<8)|(array[5]) #block3
+ tmc_z=(array[6]<<8)|(array[7])#block4
+ tmc_hash=md5.new(str([PI,tmc_x,tmc_y,tmc_z])).hexdigest()
+ tmc_T=tmc_x>>4 #0:TMC-message 1:tuning info/service provider name
+ tmc_F=int((tmc_x>>3)&0x1) #identifies the message as a Single Group (F = 1) or Multi Group (F = 0)
+ Y15=int(tmc_y>>15)
+ #timestring=self.RDS_data[PI]["time"]["timestring"]
+ datetime_received=self.RDS_data[PI]["time"]["datetime"]
+ if tmc_T == 0:
+ if tmc_F==1:#single group
+ tmc_msg=tmc_message(PI,tmc_x,tmc_y,tmc_z,datetime_received,self)
+ self.print_tmc_msg(tmc_msg)
+ elif tmc_F==0 and Y15==1:#1st group of multigroup
+ ci=int(tmc_x&0x7)
+ tmc_msg=tmc_message(PI,tmc_x,tmc_y,tmc_z,datetime_received,self)
+ #if self.RDS_data[PI]["internals"]["unfinished_TMC"].has_key(ci):
+ #print("overwriting parital message")
+ self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]={"msg":tmc_msg,"time":time.time()}
+ else:
+ ci=int(tmc_x&0x7)
+ if self.RDS_data[PI]["internals"]["unfinished_TMC"].has_key(ci):
+ tmc_msg=self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["msg"]
+ tmc_msg.add_group(tmc_y,tmc_z)
+ age=time.time()-self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["time"]
+ t=(time.time(),PI,age,ci,tmc_msg.is_complete)
+ #print("%f: continuing message PI:%s,age:%f,ci:%i complete:%i"%t)
+ self.RDS_data[PI]["internals"]["unfinished_TMC"][ci]["time"]=time.time()
+ if tmc_msg.is_complete:
+ self.print_tmc_msg(tmc_msg)#print and store message
+ del self.RDS_data[PI]["internals"]["unfinished_TMC"][tmc_msg.ci]#delete finished message
+ else:
+ #if not ci==0:
+ #print("ci %i not found, discarding"%ci)
+ pass
+
+ else:#alert plus or provider info
+ adr=tmc_x&0xf
+ if 4 <= adr and adr <= 9:
+ #seen variants 4569, 6 most often
+ #print("TMC-info variant:%i"%adr)
+ if adr==4 or adr==5:#service provider name
+ segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7]))
+ if self.debug:
+ print("TMC-info adr:%i (provider name), segment:%s, station:%s"%(adr,segment,self.RDS_data[PI]["PSN"]))
+ if self.RDS_data[PI]["AID_list"].has_key(52550):
+ text_list=list(self.RDS_data[PI]["AID_list"][52550]["provider name"])
+ seg_adr_start=(adr-4)*4#start of segment
+ text_list[seg_adr_start:seg_adr_start+4]=segment
+ self.RDS_data[PI]["AID_list"][52550]["provider name"]="".join(text_list)
+ if adr== 7:#freq of tuned an mapped station (not seen yet)
+ freq_TN=tmc_y>>8
+ freq_ON=tmc_y&0xff#mapped frequency
+ if self.debug:
+ print("TMC-info: TN:%i, station:%s"%(freq_TN,self.RDS_data[PI]["PSN"]))
+ self.RDS_data[PI]["TMC_TN"]=freq_TN
+ else:
+ if self.debug:
+ print("alert plus on station %s (%s)"%(PI,self.RDS_data[PI]["PSN"]))#(not seen yet)
+
+ #self.tableobj.RDS_data["D301"]["AID_list"][52550]["provider name"]="test____"
+ #RadioText+ (grouptype mostly 12A):
+ elif self.RDS_data[PI]["AID_list"].has_key(19415) and self.RDS_data[PI]["AID_list"][19415]["groupType"]==groupType:#RT+
+ if not self.RDS_data[PI].has_key("RT+"):
+ #self.RDS_data[PI]["RT+"]={"history":{},"last_item_toggle_bit":2}
+ self.RDS_data[PI]["RT+"]={"last_item_toggle_bit":2}
+ self.RDS_data[PI]["RT+_history"]={}
+ self.RDS_data[PI]["internals"]["RT+_times"]={}
+ #self.RDS_data[PI]["RT+"]["last_item_toggle_bit"]=2
+ A3_data=self.RDS_data[PI]["AID_list"][19415]["app_data"]
+ template_number=A3_data&0xff
+ SCB=(A3_data >> 8)&0x0f#server control bit
+ CB_flag=(A3_data>>12)&0x1 #is set if template available
+ rtp_message= ((array[3]&0x1f)<<32)|(array[4]<<24)|(array[5]<<16)|(array[6]<<8)|(array[7])
+ item_toggle_bit=(rtp_message>>36)&0x1
+ item_running_bit=(rtp_message>>35)&0x1
+ tag1=(rtp_message>>17)&(2**18-1)#6+6+6
+ tag2=(rtp_message)&(2**17-1)#6+6+5
+ tag1_type=self.rtp_classnames[int(tag1>>12)]
+ tag2_type=self.rtp_classnames[int(tag2>>11)]
+ tag1_start=int((tag1>>6)&(2**6-1))
+ tag1_len=int(tag1&(2**6-1))
+ tag2_start=int((tag2>>5)&(2**6-1))
+ tag2_len=int(tag2&(2**5-1))
+ if not self.RDS_data[PI]["RT+"]["last_item_toggle_bit"] == item_toggle_bit: #new item
+ #self.RDS_data[PI]["RT+"]["history"][str(datetime.now())]=self.RDS_data[PI]["internals"]["last_rt_tooltip"]
+ t=(str(datetime.now()),PI,self.RDS_data[PI]["PSN"],"RT+",str(self.RDS_data[PI]["RT+"]))
+ if self.writeDB:
+ db.execute("INSERT INTO data (time,PI,PSN,dataType,data) VALUES (?,?,?,?,?)",t)
+ self.RDS_data[PI]["RT+_history"][str(datetime.now())]=copy.deepcopy(self.RDS_data[PI]["RT+"])#save old item
+ self.RDS_data[PI]["RT+"]["last_item_toggle_bit"] = item_toggle_bit
+ rtcol=self.colorder.index('text')
+ if self.debug:
+ print("toggle bit changed on PI:%s, cleared RT-tt"%PI)
+ self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'tooltip':""})
+ if self.RDS_data[PI].has_key("RT_0"):
+ ab_flag=self.RDS_data[PI]["RT_last_ab_flag"]
+ rt=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]
+ rt_valid=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"]
+ if not tag1_type=="DUMMY_CLASS" and all(rt_valid[tag1_start:tag1_start+tag1_len+1]):
+ self.RDS_data[PI]["RT+"][tag1_type]=rt[tag1_start:tag1_start+tag1_len+1]
+ self.RDS_data[PI]["internals"]["RT+_times"][tag1_type]=time.time()
+ if not tag2_type=="DUMMY_CLASS" and all(rt_valid[tag2_start:tag2_start+tag2_len+1]):
+ self.RDS_data[PI]["RT+"][tag2_type]=rt[tag2_start:tag2_start+tag2_len+1]
+ self.RDS_data[PI]["internals"]["RT+_times"][tag2_type]=time.time()
+ #check outdated tags:
+ for tagtype in self.RDS_data[PI]["internals"]["RT+_times"].keys():#.keys() makes copy to avoid RuntimeError: dictionary changed size during iteration
+ age=time.time()-self.RDS_data[PI]["internals"]["RT+_times"][tagtype]
+ if age>90:#delete if older than 90 sek#TODO delete if toggle bit changes?, delete if tag changes? (title change -> delete artist)
+ del self.RDS_data[PI]["internals"]["RT+_times"][tagtype]
+ del self.RDS_data[PI]["RT+"][tagtype]
+
+
+ tags="ir:%i,it:%i"%(item_running_bit,item_toggle_bit)
+ rtpcol=self.colorder.index('RT+')
+ self.signals.DataUpdateEvent.emit({'col':rtpcol,'row':port,'PI':PI,'string':tags})
+ if(tag2_type=="ITEM.TITLE" and self.RDS_data[PI].has_key("RT_0")):#TODO remove duplicate code
+ ab_flag=self.RDS_data[PI]["RT_last_ab_flag"]
+ rt=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT"]
+ rt_valid=self.RDS_data[PI]["RT_"+str(ab_flag)]["RT_valid"]
+ artist="?"
+ song="?"
+ if all(rt_valid[tag1_start:tag1_start+tag1_len+1]):
+ artist=rt[tag1_start:tag1_start+tag1_len+1]
+ if all(rt_valid[tag2_start:tag2_start+tag2_len+1]):
+ song=rt[tag2_start:tag2_start+tag2_len+1]
+ formatted_text="%s by %s"%(song,artist)
+ rtcol=self.colorder.index('text')
+ #only update tooltip if text changed -> remove flicker, still flickers :(
+ if not formatted_text == self.RDS_data[PI]["internals"]["last_rt_tooltip"]:
+ self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'tooltip':formatted_text})
+ self.RDS_data[PI]["internals"]["last_rt_tooltip"] = formatted_text
+ #elif(not tag1_type=="ITEM.ARTIST" and not tag1_type=="DUMMY_CLASS"):
+ # print("%s:RT+: tag1_type:%s, tag2_type:%s"%(PI,tag1_type,tag2_type))
+ elif (groupType == "14A"):#EON enhanced other networks
+ #TN = tuned network, ON=other network
+ if not self.RDS_data[PI].has_key("EON"):
+ self.RDS_data[PI]["EON"]={}
+ TP_ON=(array[3]>>4)&0x1
+ PI_ON="%02X%02X" %(array[6],array[7])
+ variant=array[3]&0xf
+ self.signals.DataUpdateEvent.emit({'PI':PI_ON,'TP':TP_ON})
+ if not self.RDS_data.has_key(PI_ON):
+ self.init_data_for_PI(PI_ON)
+ self.RDS_data[PI_ON]["TP"]=TP_ON
+ self.PI_dict[PI_ON]=0#initialize dict, even if no packets received
+ if self.log:
+ print("found station %s via EON on station %s"%(PI_ON,PI))
+ if not self.RDS_data[PI]["EON"].has_key(PI_ON):
+ self.RDS_data[PI]["EON"][PI_ON]={}
+ self.RDS_data[PI]["EON"][PI_ON]["PSN"]="_"*8
+ if variant in range(4):#variant 0..3 -> PS_ON
+ segment=self.decode_chars(chr(array[4])+chr(array[5]))
+ name_list=list(self.RDS_data[PI_ON]["PSN"])
+ #name_list=list(self.RDS_data[PI]["EON"][PI_ON]["PSN"])
+ name_list[variant*2:variant*2+2]=segment
+ if (name_list[variant*2:variant*2+2]==list(segment)):#segment already there
+ segmentcolor="purple"
+ elif(name_list[variant*2:variant*2+2]==['_']*2): #segment new
+ segmentcolor="purple"
+ name_list[variant*2:variant*2+2]=segment
+ else:#name changed (böse)
+ segmentcolor="red"
+ name_list=['_']*8 #reset name
+ name_list[variant*2:variant*2+2]=segment
+ #reset stored text:
+ self.RDS_data[PI_ON]["PSN"]="_"*8
+ self.RDS_data[PI_ON]["PSN_valid"]=[False]*8
+ self.RDS_data[PI_ON]["PSN_valid"][variant*2:variant*2+2]=[True] *2
+ PS_ON_str="".join(name_list)
+ self.RDS_data[PI_ON]["PSN"]=PS_ON_str
+ self.RDS_data[PI]["EON"][PI_ON]["PSN"]=PS_ON_str
+ #determine if text is valid
+ valid=True
+ for i in range(0,8):
+ if (not self.RDS_data[PI_ON]["PSN_valid"][i]):
+ valid = False
+ if(valid):
+ #textcolor="black"
+ textcolor=""#use default color (white if background is black)
+
+ else:
+ textcolor="gray"
+ formatted_text=self.color_text(self.RDS_data[PI_ON]["PSN"],variant*2,variant*2+2,textcolor,segmentcolor)
+ self.RDS_data[PI]["EON"][PI_ON]["PSN"]=PS_ON_str
+ self.RDS_data[PI_ON]["PSN"]=PS_ON_str
+ #formatted_text="%s"%("purple",PS_ON_str)
+ self.signals.DataUpdateEvent.emit({'PI':PI_ON,'PSN':formatted_text})
+ try:
+ t=(PI_ON,self.RDS_data[PI_ON]["PSN"],float(self.RDS_data[PI_ON]["AF"]["main"]),self.RDS_data[PI_ON]["PTY"],int(self.RDS_data[PI_ON]["TP"]))
+ if self.writeDB:
+ db.execute("INSERT OR REPLACE INTO stations (PI,PSN,freq,PTY,TP) VALUES (?,?,?,?,?)",t)
+ except KeyError:
+ #not all info present -> no db update
+ pass
+ if variant==4:#AF_ON
+ if self.debug:
+ print("AF_ON method A")#TODO
+ if variant in range(5,10):#variant 5..9 -> mapped freqs
+ freq_TN=self.decode_AF_freq(array[4])
+ freq_ON=self.decode_AF_freq(array[5])
+ #lock in tuned network if freq_TN matches decoder frequency
+ if(self.RDS_data[PI].has_key("tuned_freq") and freq_TN==self.RDS_data[PI]["tuned_freq"]and not self.RDS_data[PI]["AF"].has_key("main")):
+ if self.log:
+ print("main frequency found in 14A: station:%s, freq:%0.1fM"% (self.RDS_data[PI]["PSN"],freq_TN/1e6))
+ self.RDS_data[PI]["AF"]["main"]=freq_TN
+ freq_str="EON_TN:%0.1fM"% (freq_TN/1e6)
+ self.signals.DataUpdateEvent.emit({'PI':PI,'freq':freq_str})
+ #lock in ON if TN is locked in
+ if(self.RDS_data[PI]["AF"].has_key("main") and self.RDS_data[PI]["AF"]["main"]==freq_TN and not self.RDS_data[PI_ON]["AF"].has_key("main")):
+ if self.log:
+ print("mapped frequency found in 14A: station:%s, freq:%0.1fM"% (self.RDS_data[PI_ON]["PSN"],freq_ON/1e6))
+ self.RDS_data[PI_ON]["AF"]["main"]=freq_ON
+ freq_str="EON:%0.1fM"% (freq_ON/1e6)
+ self.signals.DataUpdateEvent.emit({'PI':PI_ON,'freq':freq_str})
+ #print("mapped freq in variant %i:, %i->%i"%(variant,freq_TN,freq_ON))
+ if variant==13:#PTY and TA of ON
+ PTY_ON=array[4]>>3
+ TA_ON=array[5]&0x1
+ self.RDS_data[PI]["EON"][PI_ON]["TA_ON"]=TA_ON
+ self.RDS_data[PI]["EON"][PI_ON]["PTY_ON"]=PTY_ON
+ self.RDS_data[PI_ON]["TA"]=TA_ON
+ self.RDS_data[PI_ON]["PTY"]=self.pty_dict[PTY_ON]
+ self.signals.DataUpdateEvent.emit({'PI':PI_ON,'PTY':self.pty_dict[PTY_ON],'TA':TA_ON})
+ #rest is reserved
+ if variant==14:#programme item number of ON
+ PIN_ON=(array[4]<<8)|(array[5])
+ elif (groupType == "8A"):
+ if self.debug:
+ print("8A without 3A on PI:%s"%PI)
+ #else:#other group
+ 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]
+ return_string+=unichr(ord(char))
+ except KeyError:
+ return_string+="?%02X?"%ord(char)
+ print("symbol not decoded: "+"?%02X?"%ord(char)+"in string:"+return_string)
+ #charlist[i]='?'#symbol not decoded #TODO
+ pass
+ #return "".join(charlist)
+ return return_string
+ def color_text(self, text, start,end,textcolor,segmentcolor):
+ #formatted_text="%s%s%s"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:])
+ #formatted_text="%s%s%s"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:])
+ formatted_text=("%s"*3)% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:])
+ return formatted_text
+class rds_parser_table_qt_Widget(QtGui.QWidget):
+ def __init__(self, signals,label,tableobj,showTMC):
+ #print("gui initializing")self.tableobj.RDS_data["D3A2"]
+ self.signals = signals
+ self.tableobj=tableobj
+ self.showTMC=showTMC
+ self.signals.DataUpdateEvent.connect(self.display_data)
+ """ Creates the QT Range widget """
+ QtGui.QWidget.__init__(self)
+ layout = Qt.QVBoxLayout()
+ #self.label = Qt.QLabel(label)
+ #layout.addWidget(self.label)#title of table disabled to save space
+ #layout.addWidget(self.label)
+ self.setLayout(layout)
+ #self.decoder_to_PI={}
+ self.PI_to_row={}
+ self.table=QtGui.QTableWidget(self)
+ rowcount=0
+ self.table.setRowCount(rowcount)
+ self.table.setColumnCount(10)
+ self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) #disallow editing
+
+ #self.colorder=['ID','freq','name','buttons','PTY','AF','time','text','quality']
+ self.colorder=tableobj.colorder
+ ##button.clicked.connect(self.getDetails)
+
+ layout.addWidget(self.table)
+ self.table.setHorizontalHeaderLabels(self.colorder)
+ #self.table.setMaximumHeight(300)#TODO use dynamic value
+
+ button_layout = Qt.QHBoxLayout()
+ codebutton = QtGui.QPushButton("code.interact")
+ codebutton.clicked.connect(self.onCLick)
+ button_layout.addWidget(codebutton)
+ ih_button = QtGui.QPushButton("show IH data")
+ ih_button.clicked.connect(self.showIHdata)
+ button_layout.addWidget(ih_button)
+ save_button = QtGui.QPushButton("save")
+ save_button.clicked.connect(self.saveData)
+ button_layout.addWidget(save_button)
+ print_button = QtGui.QPushButton("print profile")
+ print_button.clicked.connect(self.printProfile)
+ button_layout.addWidget(print_button)
+ mode_button = QtGui.QPushButton("mode")
+ mode_button.clicked.connect(self.switchMode)
+ button_layout.addWidget(mode_button)
+ layout.addLayout(button_layout)
+ label_layout = Qt.QHBoxLayout()
+ self.freq_label=QtGui.QLabel("decoder frequencies:")
+ #self.freq_label.setTextFormat(QtCore.Qt.RichText)
+ #self.freq_label.setTextFormat(QtCore.Qt.PlainText)
+ self.count_label=QtGui.QLabel("count:")
+ label_layout.addWidget(self.freq_label)
+ label_layout.addWidget(self.count_label)
+ layout.addLayout(label_layout)
+ #TODO set different minsize if TMC is shown
+ self.setMinimumSize(Qt.QSize(500,40*self.tableobj.nPorts))
+ if self.showTMC:
+ self.tmc_message_label=QtGui.QLabel("TMC messages:")
+ self.event_filter=QtGui.QLineEdit()#QPlainTextEdit ?
+ self.location_filter=QtGui.QLineEdit(u"Baden-Württemberg")
+ #self.location_filter=QtGui.QLineEdit(u"")
+ self.event_filter.returnPressed.connect(self.filterChanged)
+ self.location_filter.returnPressed.connect(self.filterChanged)
+
+ filter_layout = Qt.QHBoxLayout()
+ filter_layout.addWidget(QtGui.QLabel("event filter:"))
+ filter_layout.addWidget(self.event_filter)
+ filter_layout.addWidget(QtGui.QLabel("location filter:"))
+ filter_layout.addWidget(self.location_filter)
+
+ layout.addLayout(filter_layout)
+ layout.addWidget(self.tmc_message_label)
+ self.logOutput = Qt.QTextEdit()
+ self.logOutput.setReadOnly(True)
+ self.logOutput.setLineWrapMode(Qt.QTextEdit.NoWrap)
+ self.logOutput.setMaximumHeight(150)
+ font = self.logOutput.font()
+ font.setFamily("Courier")
+ font.setPointSize(10)
+ layout.addWidget(self.logOutput)
+ self.lastResizeTime=0
+ self.clip = QtGui.QApplication.clipboard()
+ #self.cb.clear(mode=cb.Clipboard )
+ #self.cb.setText("Clipboard Text", mode=cb.Clipboard)
+ def filterChanged(self):
+ print("filter changed")
+ ef=unicode(self.event_filter.text().toUtf8(), encoding="UTF-8").lower()
+ lf=unicode(self.location_filter.text().toUtf8(), encoding="UTF-8").lower()
+ self.logOutput.clear()
+ #filters=[{"type":"location", "str":u"Baden-Württemberg"}]
+ filters=[{"type":"location", "str":lf},{"type":"event", "str":ef}]
+ self.logOutput.append(Qt.QString.fromUtf8(self.tableobj.tmc_messages.getLogString(filters)))
+ #self.logOutput.append(Qt.QString.fromUtf8(self.tableobj.tmc_messages.getLogString([])))
+ def keyPressEvent(self, e):
+ if (e.modifiers() & QtCore.Qt.ControlModifier) and len(self.table.selectedRanges())>0:
+ selected = self.table.selectedRanges().pop()
+ selected.leftColumn()
+ selected.topRow()
+ if e.key() == QtCore.Qt.Key_C: #copy
+ try:
+ qs = self.table.cellWidget(selected.topRow(),selected.leftColumn()).text()#get QString from table
+ s=re.sub("<.*?>","", str(qs))#remove html tags
+ self.clip.setText(s)
+ except Exception as e:
+ print(e)
+ print("no text, cant copy")
+ def insert_empty_row(self):
+ rowPosition = self.table.rowCount()
+ self.table.insertRow(rowPosition)
+ #for col in range(self.table.columnCount()-1):#all labels except in last column -> buttons
+# self.table.setCellWidget(rowPosition,col,QtGui.QLabel())
+ #initialize labels everywhere:
+ for col in range(self.table.columnCount()):
+ self.table.setCellWidget(rowPosition,col,QtGui.QLabel())
+ button_layout = Qt.QHBoxLayout()
+ details_button=QtGui.QPushButton("Detail")
+ details_button.clicked.connect(functools.partial(self.getDetails, row=rowPosition))
+ button_layout.addWidget(details_button)
+ 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.setWindowTitle(self.tableobj.RDS_data[PI]["PSN"])
+ view.exec_()
+ def onCLick(self):
+ print("button clicked")
+ code.interact(local=locals())
+if __name__ == "__main__":
+ from PyQt4 import Qt
+ import sys
+
+
+ app = Qt.QApplication(sys.argv)
+ mainobj= rds_parser_table_qt_Signals()
+ #mainobj=None
+ widget = rds_parser_table_qt_Widget(mainobj,"TestLabel")
+ widget.show()
+ widget.setWindowTitle("Test Qt gui")
+ widget.setGeometry(200,200,600,300)
+ sys.exit(app.exec_())
+
+ widget = None
+
+