@ -22,10 +22,11 @@
# This file contains data models, view delegates, and associated classes
# for handling the GUI back end data model.
from PyQt4 import QtCore , QtGui
from PyQt4 import QtCore , QtGui , QtSql
import air_modes
import threading , math , time
from air_modes . exceptions import *
from gnuradio . gr . pubsub import pubsub
#fades the ICAOs out as their last report gets older,
#and display ident if available, ICAO otherwise
@ -37,12 +38,16 @@ class ICAOViewDelegate(QtGui.QStyledItemDelegate):
painter . drawRect ( option . rect )
#if there's an ident available, use it. otherwise print the ICAO
if index . model ( ) . data ( index . model ( ) . index ( index . row ( ) , 9 ) ) != QtCore . QVariant ( ) :
paintstr = index . model ( ) . data ( index . model ( ) . index ( index . row ( ) , 9 ) ) . toString ( )
if index . model ( ) . data ( index . model ( ) . index ( index . row ( ) , 8 ) ) != QtCore . QVariant ( ) :
paintstr = index . model ( ) . data ( index . model ( ) . index ( index . row ( ) , 8 ) ) . toString ( )
else :
paintstr = index . model ( ) . data ( index . model ( ) . index ( index . row ( ) , 0 ) ) . toString ( )
last_report = index . model ( ) . data ( index . model ( ) . index ( index . row ( ) , 1 ) ) . toDouble ( ) [ 0 ]
age = ( time . time ( ) - last_report )
#FIXME this is kind of heinous, find out how you got int data out of it last time
last_report = time . strptime ( str ( index . model ( ) . data ( index . model ( ) . index ( index . row ( ) , 1 ) ) . toString ( ) ) , " % Y- % m- %d % H: % M: % S " )
age = ( time . mktime ( time . gmtime ( ) ) - time . mktime ( last_report ) ) - 3600. * time . daylight
print age
max_age = 60. #age at which it grays out
#minimum alpha is 0x40 (oldest), max is 0xFF (newest)
age = min ( age , max_age )
@ -50,152 +55,36 @@ class ICAOViewDelegate(QtGui.QStyledItemDelegate):
painter . setPen ( QtGui . QColor ( 0 , 0 , 0 , alpha ) )
painter . drawText ( option . rect . left ( ) + 3 , option . rect . top ( ) , option . rect . width ( ) , option . rect . height ( ) , option . displayAlignment , paintstr )
#the data model used to display dashboard data.
class dashboard_data_model ( QtCore . QAbstractTableModel ) :
def __init__ ( self , parent ) :
QtCore . QAbstractTableModel . __init__ ( self , parent )
self . _data = [ ]
self . lock = threading . Lock ( )
self . _colnames = [ " icao " , " seen " , " rssi " , " latitude " , " longitude " , " altitude " , " speed " , " heading " , " vertical " , " ident " , " type " , " range " , " bearing " ]
#custom precision limits for display
self . _precisions = [ None , None , None , 6 , 6 , 0 , 0 , 0 , 0 , None , None , 2 , 0 ]
for field in self . _colnames :
self . setHeaderData ( self . _colnames . index ( field ) , QtCore . Qt . Horizontal , field )
def rowCount ( self , parent = QtCore . QVariant ( ) ) :
return len ( self . _data )
def columnCount ( self , parent = QtCore . QVariant ( ) ) :
return len ( self . _colnames )
def data ( self , index , role = QtCore . Qt . DisplayRole ) :
if not index . isValid ( ) :
return QtCore . QVariant ( )
if index . row ( ) > = self . rowCount ( ) :
return QtCore . QVariant ( )
if index . column ( ) > = self . columnCount ( ) :
return QtCore . QVariant ( )
if ( role != QtCore . Qt . DisplayRole ) and ( role != QtCore . Qt . EditRole ) :
return QtCore . QVariant ( )
if self . _data [ index . row ( ) ] [ index . column ( ) ] is None :
return QtCore . QVariant ( )
else :
#if there's a dedicated precision for that column, print it out with the specified precision.
#this only works well if you DON'T have other views/widgets that depend on numeric data coming out.
#i don't like this, but it works for now. unfortunately it seems like Qt doesn't give you a
#good alternative.
if self . _precisions [ index . column ( ) ] is not None :
return QtCore . QVariant ( " %.*f " % ( self . _precisions [ index . column ( ) ] , self . _data [ index . row ( ) ] [ index . column ( ) ] ) )
else :
if self . _colnames [ index . column ( ) ] == " icao " :
return QtCore . QVariant ( " %06x " % self . _data [ index . row ( ) ] [ index . column ( ) ] ) #return as hex string
else :
return QtCore . QVariant ( self . _data [ index . row ( ) ] [ index . column ( ) ] )
def setData ( self , index , value , role = QtCore . Qt . EditRole ) :
self . lock . acquire ( )
if not index . isValid ( ) :
return False
if index . row ( ) > = self . rowCount ( ) :
return False
if index . column > = self . columnCount ( ) :
return False
if role != QtCore . Qt . EditRole :
return False
self . _data [ index . row ( ) ] [ index . column ( ) ] = value
self . lock . release ( )
#addRecord implements an upsert on self._data; that is,
#it updates the row if the ICAO exists, or else it creates a new row.
def addRecord ( self , record ) :
self . lock . acquire ( )
icaos = [ x [ 0 ] for x in self . _data ]
if record [ " icao " ] in icaos :
row = icaos . index ( record [ " icao " ] )
for column in record :
self . _data [ row ] [ self . _colnames . index ( column ) ] = record [ column ]
#create index to existing row and tell the model everything's changed in this row
#or inside the for loop, use dataChanged on each changed field (might be better)
self . dataChanged . emit ( self . createIndex ( row , 0 ) , self . createIndex ( row , len ( self . _colnames ) - 1 ) )
#class dashboard_sql_model(QtCore.QAbstractTableModel):
# def __init__(self, parent):
# QtCore.QAbstractTableModel.__init__(self, parent)
#only create records for ICAOs with ADS-B reports
elif ( " latitude " or " speed " or " ident " ) in record :
#find new inserted row number
icaos . append ( record [ " icao " ] )
newrowoffset = sorted ( icaos ) . index ( record [ " icao " ] )
self . beginInsertRows ( QtCore . QModelIndex ( ) , newrowoffset , newrowoffset )
newrecord = [ None for x in xrange ( len ( self . _colnames ) ) ]
for col in xrange ( 0 , len ( self . _colnames ) ) :
if self . _colnames [ col ] in record :
newrecord [ col ] = record [ self . _colnames [ col ] ]
self . _data . append ( newrecord )
self . _data = sorted ( self . _data , key = lambda x : x [ 0 ] ) #sort by icao
self . endInsertRows ( )
self . lock . release ( )
self . prune ( )
# def update(self, icao):
#weeds out ICAOs older than 5 minutes
def prune ( self ) :
self . lock . acquire ( )
for ( index , row ) in enumerate ( self . _data ) :
if time . time ( ) - row [ 1 ] > = 60 :
self . beginRemoveRows ( QtCore . QModelIndex ( ) , index , index )
self . _data . pop ( index )
self . endRemoveRows ( )
self . lock . release ( )
class dashboard_output :
def __init__ ( self , cprdec , model , pub ) :
self . model = model
self . _cpr = cprdec
pub . subscribe ( " modes_dl " , self . output )
def output ( self , msg ) :
try :
msgtype = msg . data [ " df " ]
now = time . time ( )
newrow = { " rssi " : msg . rssi , " seen " : now }
if msgtype in [ 0 , 4 , 20 ] :
newrow [ " altitude " ] = air_modes . altitude . decode_alt ( msg . data [ " ac " ] , True )
newrow [ " icao " ] = msg . ecc
self . model . addRecord ( newrow )
elif msgtype == 17 :
icao = msg . data [ " aa " ]
newrow [ " icao " ] = icao
subtype = msg . data [ " ftc " ]
if subtype == 4 :
( ident , actype ) = air_modes . parseBDS08 ( msg . data )
newrow [ " ident " ] = ident
newrow [ " type " ] = actype
elif 5 < = subtype < = 8 :
( ground_track , decoded_lat , decoded_lon , rnge , bearing ) = air_modes . parseBDS06 ( msg . data , self . _cpr )
newrow [ " heading " ] = ground_track
newrow [ " latitude " ] = decoded_lat
newrow [ " longitude " ] = decoded_lon
newrow [ " altitude " ] = 0
if rnge is not None :
newrow [ " range " ] = rnge
newrow [ " bearing " ] = bearing
elif 9 < = subtype < = 18 :
( altitude , decoded_lat , decoded_lon , rnge , bearing ) = air_modes . parseBDS05 ( msg . data , self . _cpr )
newrow [ " altitude " ] = altitude
newrow [ " latitude " ] = decoded_lat
newrow [ " longitude " ] = decoded_lon
if rnge is not None :
newrow [ " range " ] = rnge
newrow [ " bearing " ] = bearing
elif subtype == 19 :
subsubtype = msg . data [ " sub " ]
velocity = None
heading = None
vert_spd = None
if subsubtype == 0 :
( velocity , heading , vert_spd ) = air_modes . parseBDS09_0 ( msg . data )
elif 1 < = subsubtype < = 2 :
( velocity , heading , vert_spd ) = air_modes . parseBDS09_1 ( msg . data )
newrow [ " speed " ] = velocity
newrow [ " heading " ] = heading
newrow [ " vertical " ] = vert_spd
self . model . addRecord ( newrow )
except ADSBError :
return
#TODO must add libqt4-sql, libqt4-sql-sqlite, python-qt4-sql to dependencies
#TODO looks like you're going to have to either integrate this into sql.py (ugh!) or find a way to keep it in sync
#seems like it wants to have control over maintaining data currency
#worst case is you make your own damn SQL query model based on abstracttablemodel.
class dashboard_sql_model ( QtSql . QSqlQueryModel ) :
def __init__ ( self , parent ) :
QtSql . QSqlQueryModel . __init__ ( self , parent )
self . _query = """ select tab1.icao, tab1.seen, tab1.lat, tab1.lon, tab1.alt, speed, heading, vertical, ident, type
from ( select * from ( select * from positions order by seen desc ) group by icao ) tab1
left join ( select * from ( select * from vectors order by seen desc ) group by icao ) tab2
on tab1 . icao = tab2 . icao
left join ( select * from ( select * from ident ) ) tab3
on tab1 . icao = tab3 . icao
where tab1 . seen > datetime ( ' now ' , ' -1 minute ' ) """
self . _sql = None
self . _db = QtSql . QSqlDatabase ( " QSQLITE " )
self . _db . setDatabaseName ( " adsb.db " ) #TODO specify this elsewhere
self . _db . open ( )
#what is this i don't even
#fetches the combined data of all three tables for all ICAOs seen in the last minute.
#FIXME PyQt's SQLite gives you different results than the SQLite browser
self . setQuery ( self . _query , self . _db )
#the big club
def update_all ( self , icao ) :
self . setQuery ( self . _query , self . _db )
#self.dataChanged.emit(self.createIndex(0, 0), self.createIndex(self.rowCount(), self.columnCount()))