Merge pull request #55 from Meisterschueler/device_info

Device info
pull/58/head
Meisterschueler 2016-06-26 19:23:59 +02:00 zatwierdzone przez GitHub
commit c87bb0f546
14 zmienionych plików z 305 dodań i 165 usunięć

Wyświetl plik

@ -0,0 +1,62 @@
"""Added DeviceInfo
Revision ID: 4ebfb325db6
Revises: 163f6213d3f
Create Date: 2016-06-04 11:11:00.546524
"""
# revision identifiers, used by Alembic.
revision = '4ebfb325db6'
down_revision = '163f6213d3f'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
op.execute("CREATE TABLE device_info AS SELECT * FROM device;")
op.create_index('ix_device_info_address', 'device_info', ['address'])
op.drop_column('device_info', 'name')
op.drop_column('device_info', 'airport')
op.drop_column('device_info', 'frequency')
op.drop_column('device', 'address_origin')
op.drop_column('device', 'name')
op.drop_column('device', 'airport')
op.drop_column('device', 'aircraft')
op.drop_column('device', 'registration')
op.drop_column('device', 'competition')
op.drop_column('device', 'frequency')
op.drop_column('device', 'tracked')
op.drop_column('device', 'identified')
op.add_column('device', sa.Column('stealth', sa.Boolean))
op.add_column('device', sa.Column('software_version', sa.Float))
op.add_column('device', sa.Column('hardware_version', sa.SmallInteger))
op.add_column('device', sa.Column('real_address', sa.String(6)))
def downgrade():
op.add_column('device', sa.Column('address_origin', sa.SmallInteger))
op.add_column('device', sa.Column('name', sa.Unicode))
op.add_column('device', sa.Column('airport', sa.String))
op.add_column('device', sa.Column('aircraft', sa.String))
op.add_column('device', sa.Column('registration', sa.String(7)))
op.add_column('device', sa.Column('competition', sa.String(3)))
op.add_column('device', sa.Column('frequency', sa.String))
op.add_column('device', sa.Column('tracked', sa.Boolean))
op.add_column('device', sa.Column('identified', sa.Boolean))
op.create_index('ix_device_info_registration', 'device', ['registration'])
op.drop_column('device', 'stealth')
op.drop_column('device', 'software_version')
op.drop_column('device', 'hardware_version')
op.drop_column('device', 'real_address')
# transfer from device_info to device costs too much...
op.execute("DROP TABLE device_info;")
pass

Wyświetl plik

@ -1,8 +1,6 @@
from sqlalchemy.sql import null
from celery.utils.log import get_task_logger
from ogn.model import Device, AddressOrigin
from ogn.model import DeviceInfo, AddressOrigin
from ogn.utils import get_ddb
from ogn.collect.celery import app
@ -10,45 +8,16 @@ from ogn.collect.celery import app
logger = get_task_logger(__name__)
temp_address_origin = 7
def add_devices(session, origin):
before_sq = session.query(Device.address) \
.filter(Device.address_origin == origin) \
.subquery()
add_query = session.query(Device) \
.filter(Device.address_origin == temp_address_origin) \
.filter(~Device.address.in_(before_sq))
result = add_query.update({Device.address_origin: origin},
synchronize_session='fetch')
return result
def update_devices(session, origin, devices):
session.query(Device) \
.filter(Device.address_origin == temp_address_origin) \
.delete()
session.bulk_save_objects(devices)
# mark temporary added devices
session.query(Device) \
.filter(Device.address_origin == null()) \
.update({Device.address_origin: temp_address_origin})
logger.info('Added {} devices'.format(add_devices(session, origin)))
# delete temporary added devices
session.query(Device) \
.filter(Device.address_origin == temp_address_origin) \
def update_device_infos(session, address_origin, device_infos):
session.query(DeviceInfo) \
.filter(DeviceInfo.address_origin == address_origin) \
.delete()
session.bulk_save_objects(device_infos)
session.commit()
return len(devices)
return len(device_infos)
@app.task
@ -56,7 +25,8 @@ def import_ddb():
"""Import registered devices from the DDB."""
logger.info("Import registered devices fom the DDB...")
counter = update_devices(app.session, AddressOrigin.ogn_ddb, get_ddb())
counter = update_device_infos(app.session, AddressOrigin.ogn_ddb,
get_ddb())
logger.info("Imported {} devices.".format(counter))
@ -65,6 +35,6 @@ def import_file(path='tests/custom_ddb.txt'):
"""Import registered devices from a local file."""
logger.info("Import registered devices from '{}'...".format(path))
counter = update_devices(app.session, AddressOrigin.user_defined,
get_ddb(path))
counter = update_device_infos(app.session, AddressOrigin.user_defined,
get_ddb(path))
logger.info("Imported {} devices.".format(counter))

Wyświetl plik

@ -26,25 +26,23 @@ def compute_takeoff_and_landing():
airport_radius = 0.025 # takeoff / landing must not exceed this radius (degree!) around the airport
airport_delta = 100 # takeoff / landing must not exceed this altitude offset above/below the airport
# calculate the start (and stop) timestamp for the computatio
last_takeoff_landing_query = app.session.query(func.max(TakeoffLanding.timestamp))
begin_computation = last_takeoff_landing_query.one()[0]
if begin_computation is None:
# if the table is empty
last_takeoff_landing_query = app.session.query(func.min(AircraftBeacon.timestamp))
begin_computation = last_takeoff_landing_query.one()[0]
if begin_computation is None:
return 0
else:
# we get the beacons async. to be safe we delete takeoffs/landings from last 24 hours and recalculate from then
begin_computation = begin_computation - timedelta(hours=24)
app.session.query(TakeoffLanding) \
.filter(TakeoffLanding.timestamp >= begin_computation) \
.delete()
end_computation = begin_computation + timedelta(days=5)
# max AircraftBeacon id offset computed per function call
max_id_offset = 500000
logger.debug("Calculate takeoffs and landings between {} and {}"
.format(begin_computation, end_computation))
# get the last AircraftBeacon used for TakeoffLanding and start from there
last_takeoff_landing_query = app.session.query(func.max(TakeoffLanding.id).label('max_id')) \
.subquery()
last_used_aircraft_beacon_query = app.session.query(AircraftBeacon.id) \
.filter(TakeoffLanding.id == last_takeoff_landing_query.c.max_id) \
.filter(and_(AircraftBeacon.timestamp == TakeoffLanding.timestamp,
AircraftBeacon.device_id == TakeoffLanding.device_id))
last_used_aircraft_beacon_id = last_used_aircraft_beacon_query.first()
if last_used_aircraft_beacon_id is None:
aircraft_beacon_id_start = 0
else:
aircraft_beacon_id_start = last_used_aircraft_beacon_id[0] + 1
# make a query with current, previous and next position
sq = app.session.query(
@ -66,8 +64,7 @@ def compute_takeoff_and_landing():
AircraftBeacon.device_id,
func.lag(AircraftBeacon.device_id).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('device_id_prev'),
func.lead(AircraftBeacon.device_id).over(order_by=and_(AircraftBeacon.device_id, AircraftBeacon.timestamp)).label('device_id_next')) \
.filter(AircraftBeacon.timestamp >= begin_computation) \
.filter(AircraftBeacon.timestamp <= end_computation) \
.filter(between(AircraftBeacon.id, aircraft_beacon_id_start, aircraft_beacon_id_start + max_id_offset)) \
.subquery()
# find possible takeoffs and landings
@ -115,6 +112,6 @@ def compute_takeoff_and_landing():
result = app.session.execute(ins)
counter = result.rowcount
app.session.commit()
logger.debug("New/recalculated takeoffs and landings: {}".format(counter))
logger.debug("New takeoffs and landings: {}".format(counter))
return counter

Wyświetl plik

@ -2,6 +2,7 @@ from .database import manager as database_manager
from .showairport import manager as show_airport_manager
from .showreceiver import manager as show_receiver_manager
from .showdevices import manager as show_devices_manager
from .showdeviceinfos import manager as show_deviceinfos_manager
from .logbook import manager as logbook_manager
from manager import Manager
@ -12,4 +13,5 @@ manager.merge(database_manager, namespace='db')
manager.merge(show_airport_manager, namespace='show.airport')
manager.merge(show_receiver_manager, namespace='show.receiver')
manager.merge(show_devices_manager, namespace='show.devices')
manager.merge(show_deviceinfos_manager, namespace='show.deviceinfos')
manager.merge(logbook_manager, namespace='logbook')

Wyświetl plik

@ -1,7 +1,7 @@
from ogn.commands.dbutils import engine, session
from ogn.model import Base, AddressOrigin
from ogn.utils import get_ddb, get_airports
from ogn.collect.database import update_devices
from ogn.collect.database import update_device_infos
from manager import Manager
manager = Manager()
@ -50,7 +50,9 @@ def import_ddb():
"""Import registered devices from the DDB."""
print("Import registered devices fom the DDB...")
counter = update_devices(session, AddressOrigin.ogn_ddb, get_ddb())
address_origin = AddressOrigin.ogn_ddb
counter = update_device_infos(session, address_origin,
get_ddb(address_origin=address_origin))
print("Imported %i devices." % counter)
@ -60,8 +62,9 @@ def import_file(path='tests/custom_ddb.txt'):
# (flushes previously manually imported entries)
print("Import registered devices from '{}'...".format(path))
counter = update_devices(session, AddressOrigin.user_defined,
get_ddb(path))
address_origin = AddressOrigin.user_defined
counter = update_device_infos(session, address_origin,
get_ddb(csvfile=path, address_origin=address_origin))
print("Imported %i devices." % counter)

Wyświetl plik

@ -7,7 +7,7 @@ from sqlalchemy import and_, or_
from sqlalchemy.sql.expression import true, false, label
from sqlalchemy.orm import aliased
from ogn.model import Device, TakeoffLanding, Airport
from ogn.model import Device, DeviceInfo, TakeoffLanding, Airport
from ogn.commands.dbutils import session
from ogn.collect.logbook import compute_takeoff_and_landing
@ -25,7 +25,7 @@ def compute():
print("New/recalculated takeoffs/landings: {}".format(counter))
@manager.arg('date', help='date (format: yyyy-mm-dd')
@manager.arg('date', help='date (format: yyyy-mm-dd)')
@manager.arg('utc_delta_hours', help='delta hours to utc (for local time logs)')
@manager.command
def show(airport_name, utc_delta_hours=0, date=None):
@ -170,6 +170,14 @@ def show(airport_name, utc_delta_hours=0, date=None):
.subquery()
# get aircraft and airport informations and sort all entries by the reference time
sq2 = session.query(DeviceInfo.address, func.max(DeviceInfo.address_origin).label('address_origin')) \
.group_by(DeviceInfo.address) \
.subquery()
sq3 = session.query(DeviceInfo.address, DeviceInfo.registration, DeviceInfo.aircraft) \
.filter(and_(DeviceInfo.address == sq2.c.address, DeviceInfo.address_origin == sq2.c.address_origin)) \
.subquery()
takeoff_airport = aliased(Airport, name='takeoff_airport')
landing_airport = aliased(Airport, name='landing_airport')
logbook_query = session.query(union_query.c.reftime,
@ -180,12 +188,16 @@ def show(airport_name, utc_delta_hours=0, date=None):
union_query.c.landing_track,
landing_airport,
union_query.c.duration,
Device) \
.outerjoin(Device, union_query.c.device_id == Device.id) \
Device,
sq3.c.registration,
sq3.c.aircraft) \
.outerjoin(takeoff_airport, union_query.c.takeoff_airport_id == takeoff_airport.id) \
.outerjoin(landing_airport, union_query.c.landing_airport_id == landing_airport.id) \
.outerjoin(Device, union_query.c.device_id == Device.id) \
.outerjoin(sq3, sq3.c.address == Device.address) \
.order_by(union_query.c.reftime)
# ... and finally print out the logbook
print('--- Logbook ({}) ---'.format(airport_name))
def none_datetime_replacer(datetime_object):
@ -197,11 +209,11 @@ def show(airport_name, utc_delta_hours=0, date=None):
def none_timedelta_replacer(timedelta_object):
return '--:--:--' if timedelta_object is None else timedelta_object
def none_registration_replacer(device_object):
return '[' + device_object.address + ']' if device_object.registration is None else device_object.registration
def none_registration_replacer(device_object, registration_object):
return '[' + device_object.address + ']' if registration_object is None else registration_object
def none_aircraft_replacer(device_object):
return '(unknown)' if device_object.aircraft is None else device_object.aircraft
def none_aircraft_replacer(device_object, aircraft_object):
return '(unknown)' if aircraft_object is None else aircraft_object
def airport_marker(takeoff_airport_object, landing_airport_object):
if takeoff_airport_object is not None and takeoff_airport_object.name is not airport.name:
@ -211,7 +223,7 @@ def show(airport_name, utc_delta_hours=0, date=None):
else:
return ('')
for [reftime, takeoff, takeoff_track, takeoff_airport, landing, landing_track, landing_airport, duration, device] in logbook_query.all():
for [reftime, takeoff, takeoff_track, takeoff_airport, landing, landing_track, landing_airport, duration, device, registration, aircraft] in logbook_query.all():
print('%10s %8s (%2s) %8s (%2s) %8s %8s %17s %20s' % (
reftime.date(),
none_datetime_replacer(takeoff),
@ -219,6 +231,6 @@ def show(airport_name, utc_delta_hours=0, date=None):
none_datetime_replacer(landing),
none_track_replacer(landing_track),
none_timedelta_replacer(duration),
none_registration_replacer(device),
none_aircraft_replacer(device),
none_registration_replacer(device, registration),
none_aircraft_replacer(device, aircraft),
airport_marker(takeoff_airport, landing_airport)))

Wyświetl plik

@ -0,0 +1,62 @@
from ogn.commands.dbutils import session
from ogn.model import AddressOrigin
from sqlalchemy import func, and_, true, false
from manager import Manager
from ogn.model.device_info import DeviceInfo
manager = Manager()
def get_devices_stats(session):
sq_default = session.query(DeviceInfo.address) \
.filter(and_(DeviceInfo.tracked == true(), DeviceInfo.identified == true())) \
.subquery()
sq_nt = session.query(DeviceInfo.address) \
.filter(and_(DeviceInfo.tracked == false(), DeviceInfo.identified == true())) \
.subquery()
sq_ni = session.query(DeviceInfo.address) \
.filter(and_(DeviceInfo.tracked == true(), DeviceInfo.identified == false())) \
.subquery()
sq_ntni = session.query(DeviceInfo.address) \
.filter(and_(DeviceInfo.tracked == false(), DeviceInfo.identified == false())) \
.subquery()
query = session.query(DeviceInfo.address_origin,
func.count(DeviceInfo.id),
func.count(sq_default.c.address),
func.count(sq_nt.c.address),
func.count(sq_ni.c.address),
func.count(sq_ntni.c.address)) \
.outerjoin(sq_default, sq_default.c.address == DeviceInfo.address) \
.outerjoin(sq_nt, sq_nt.c.address == DeviceInfo.address) \
.outerjoin(sq_ni, sq_ni.c.address == DeviceInfo.address) \
.outerjoin(sq_ntni, sq_ntni.c.address == DeviceInfo.address) \
.group_by(DeviceInfo.address_origin)
stats = {}
for [address_origin, device_count, default_count, nt_count, ni_count, ntni_count] in query.all():
origin = AddressOrigin(address_origin).name()
stats[origin] = {'device_count': device_count,
'default_count': default_count,
'nt_count': nt_count,
'ni_count': ni_count,
'ntni_count': ntni_count}
return stats
@manager.command
def stats():
"""Show some stats on registered devices."""
print('--- Devices ---')
stats = get_devices_stats(session)
for origin in stats:
print('{:12s} Total:{:5d} - default:{:3d}, just not tracked:{:3d}, just not identified:{:3d}, not tracked & not identified: {:3d}'
.format(origin,
stats[origin]['device_count'],
stats[origin]['default_count'],
stats[origin]['nt_count'],
stats[origin]['ni_count'],
stats[origin]['ntni_count']))

Wyświetl plik

@ -1,53 +1,55 @@
from ogn.commands.dbutils import session
from ogn.model import AddressOrigin, Device
from sqlalchemy import func, and_, true, false
from ogn.model import Device, AircraftType
from sqlalchemy import func
from manager import Manager
manager = Manager()
def get_devices_stats(session):
sq_nt = session.query(Device.address) \
.filter(and_(Device.tracked == false(), Device.identified == true())) \
.subquery()
sq_ni = session.query(Device.address) \
.filter(and_(Device.tracked == true(), Device.identified == false())) \
.subquery()
sq_ntni = session.query(Device.address) \
.filter(and_(Device.tracked == false(), Device.identified == false())) \
.subquery()
query = session.query(Device.address_origin,
func.count(Device.id),
func.count(sq_nt.c.address),
func.count(sq_ni.c.address),
func.count(sq_ntni.c.address)) \
.outerjoin(sq_nt, sq_nt.c.address == Device.address) \
.outerjoin(sq_ni, sq_ni.c.address == Device.address) \
.outerjoin(sq_ntni, sq_ntni.c.address == Device.address) \
.group_by(Device.address_origin)
stats = {}
for [address_origin, device_count, nt_count, ni_count, ntni_count] in query.all():
origin = AddressOrigin(address_origin).name()
stats[origin] = {'device_count': device_count,
'nt_count': nt_count,
'ni_count': ni_count,
'ntni_count': ntni_count}
return stats
@manager.command
def aircraft_type_stats():
"""Show stats about aircraft types used by devices."""
aircraft_type_query = session.query(Device.aircraft_type,
func.count(Device.id)) \
.group_by(Device.aircraft_type) \
.order_by(func.count(Device.id).desc())
print("--- Aircraft types ---")
for [aircraft_type, count] in aircraft_type_query.all():
at = AircraftType(aircraft_type)
print("{}: {}".format(at.name(), count))
@manager.command
def stats():
"""Show some stats on registered devices."""
print('--- Devices ---')
stats = get_devices_stats(session)
for origin in stats:
print('{:12s} Total:{:5d} - not tracked:{:3d}, not identified:{:3d}, not tracked & not identified: {:3d}'
.format(origin,
stats[origin]['device_count'],
stats[origin]['nt_count'],
stats[origin]['ni_count'],
stats[origin]['ntni_count']))
def stealth_stats():
"""Show stats about stealth flag set by devices."""
stealth_query = session.query(Device.stealth,
func.count(Device.id)) \
.group_by(Device.stealth) \
.order_by(func.count(Device.id).desc())
print("--- Stealth ---")
for [is_stealth, count] in stealth_query.all():
print("{}: {}".format(is_stealth, count))
@manager.command
def software_stats():
"""Show stats about software version used by devices."""
software_query = session.query(Device.software_version,
func.count(Device.id)) \
.group_by(Device.software_version) \
.order_by(func.count(Device.id).desc())
print("--- Software version ---")
for [software_version, count] in software_query.all():
print("{}: {}".format(software_version, count))
@manager.command
def hardware_stats():
"""Show stats about hardware version used by devices."""
hardware_query = session.query(Device.hardware_version,
func.count(Device.id)) \
.group_by(Device.hardware_version) \
.order_by(func.count(Device.id).desc())
print("\n--- Hardware version ---")
for [hardware_version, count] in hardware_query.all():
print("{}: {}".format(hardware_version, count))

Wyświetl plik

@ -2,7 +2,6 @@ import logging
from ogn.commands.dbutils import session
from ogn.model import AircraftBeacon, ReceiverBeacon, Device, Receiver, Location
from ogn.parser import parse_aprs, parse_ogn_receiver_beacon, parse_ogn_aircraft_beacon, ParseError
from ogn.model.address_origin import AddressOrigin
logger = logging.getLogger(__name__)
@ -54,17 +53,25 @@ def process_beacon(raw_message):
beacon = AircraftBeacon(**message)
# connect beacon with device
device = session.query(Device.id) \
device = session.query(Device) \
.filter(Device.address == beacon.address) \
.order_by(Device.address_origin) \
.first()
if device is None:
device = Device()
device.address = beacon.address
device.address_origin = AddressOrigin.seen
session.add(device)
beacon.device_id = device.id
# update device
device.aircraft_type = beacon.aircraft_type
device.stealth = beacon.stealth
if beacon.hardware_version is not None:
device.hardware_version = beacon.hardware_version
if beacon.software_version is not None:
device.software_version = beacon.software_version
if beacon.real_address is not None:
device.real_address = beacon.real_address
# connect beacon with receiver
receiver = session.query(Receiver.id) \
.filter(Receiver.name == beacon.receiver_name) \

Wyświetl plik

@ -4,6 +4,7 @@ from .aircraft_type import AircraftType
from .base import Base
from .beacon import Beacon
from .device import Device
from .device_info import DeviceInfo
from .aircraft_beacon import AircraftBeacon
from .receiver_beacon import ReceiverBeacon
from .receiver import Receiver

Wyświetl plik

@ -1,22 +1,22 @@
class AddressOrigin:
unknown = 0
ogn_ddb = 1
flarmnet = 2
user_defined = 3
seen = 4
def __init__(self, origin):
if origin in [1, 2, 3, 4]:
if origin in [0, 1, 2, 3]:
self.origin = origin
else:
raise ValueError('no address origin with id {} known'.format(origin))
def name(self):
if self.origin == self.ogn_ddb:
if self.origin == self.unknown:
return 'unknown'
elif self.origin == self.ogn_ddb:
return 'OGN-DDB'
elif self.origin == self.flarmnet:
return 'FlarmNet'
elif self.origin == self.user_defined:
return 'user-defined'
elif self.origin == self.seen:
return 'seen'
return ''

Wyświetl plik

@ -1,4 +1,4 @@
from sqlalchemy import Column, Integer, String, Unicode, Boolean, SmallInteger
from sqlalchemy import Column, Integer, String, Float, Boolean, SmallInteger
from sqlalchemy.orm import relationship
from .base import Base
@ -8,32 +8,21 @@ class Device(Base):
__tablename__ = 'device'
id = Column(Integer, primary_key=True)
address_type = None
address = Column(String(6), index=True)
name = Column(Unicode)
airport = Column(String)
aircraft = Column(String)
registration = Column(String(7), index=True)
competition = Column(String(3))
frequency = Column(String)
tracked = Column(Boolean)
identified = Column(Boolean)
aircraft_type = Column(SmallInteger, index=True)
address_origin = Column(SmallInteger)
stealth = Column(Boolean)
software_version = Column(Float)
hardware_version = Column(SmallInteger)
real_address = Column(String(6))
# Relations
aircraft_beacons = relationship('AircraftBeacon')
def __repr__(self):
return "<Device: %s,%s,%s,%s,%s,%s,%s,%s,%s,%s>" % (
self.address_type,
return "<Device: %s,%s,%s,%s,%s,%s>" % (
self.address,
self.name,
self.airport,
self.aircraft,
self.registration,
self.competition,
self.frequency,
self.tracked,
self.identified)
self.aircraft_type,
self.stealth,
self.software_version,
self.hardware_version,
self.real_address)

Wyświetl plik

@ -0,0 +1,32 @@
from sqlalchemy import Column, Integer, String, Boolean, SmallInteger
from .base import Base
class DeviceInfo(Base):
__tablename__ = 'device_info'
id = Column(Integer, primary_key=True)
address_type = None
address = Column(String(6), index=True)
aircraft = Column(String)
registration = Column(String(7))
competition = Column(String(3))
tracked = Column(Boolean)
identified = Column(Boolean)
aircraft_type = Column(SmallInteger)
address_origin = Column(SmallInteger)
def __repr__(self):
return "<DeviceInfo: %s,%s,%s,%s,%s,%s,%s,%s,%s,%s>" % (
self.address_type,
self.address,
self.name,
self.airport,
self.aircraft,
self.registration,
self.competition,
self.frequency,
self.tracked,
self.identified)

Wyświetl plik

@ -2,7 +2,7 @@ import requests
import csv
from io import StringIO
from .model import Device, Airport, Location
from .model import AddressOrigin, DeviceInfo, Airport, Location
from geopy.geocoders import Nominatim
from geopy.exc import GeopyError
@ -21,7 +21,7 @@ nm2m = 1852
mi2m = 1609.34
def get_ddb(csvfile=None):
def get_ddb(csvfile=None, address_origin=AddressOrigin.unknown):
if csvfile is None:
r = requests.get(DDB_URL)
rows = '\n'.join(i for i in r.text.splitlines() if i[0] != '#')
@ -31,21 +31,22 @@ def get_ddb(csvfile=None):
data = csv.reader(StringIO(rows), quotechar="'", quoting=csv.QUOTE_ALL)
devices = list()
device_infos = list()
for row in data:
device = Device()
device.address_type = row[0]
device.address = row[1]
device.aircraft = row[2]
device.registration = row[3]
device.competition = row[4]
device.tracked = row[5] == 'Y'
device.identified = row[6] == 'Y'
device.aircraft_type = int(row[7])
device_info = DeviceInfo()
device_info.address_type = row[0]
device_info.address = row[1]
device_info.aircraft = row[2]
device_info.registration = row[3]
device_info.competition = row[4]
device_info.tracked = row[5] == 'Y'
device_info.identified = row[6] == 'Y'
device_info.aircraft_type = int(row[7])
device_info.address_origin = address_origin
devices.append(device)
device_infos.append(device_info)
return devices
return device_infos
def get_trackable(ddb):