252 lines
8.4 KiB
Python
252 lines
8.4 KiB
Python
from __future__ import absolute_import, division, print_function, \
|
|
unicode_literals
|
|
|
|
import json
|
|
import logging
|
|
from os import path
|
|
from uuid import uuid4
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth.models import User
|
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
from django.db import models
|
|
from django.db.models import OuterRef, Subquery
|
|
from django.db.models.signals import post_save, pre_save
|
|
from django.utils.timezone import now
|
|
from markdown import markdown
|
|
from shortuuidfield import ShortUUIDField
|
|
|
|
from db.base.helpers import gridsquare
|
|
|
|
logger = logging.getLogger('db')
|
|
|
|
DATA_SOURCES = ['manual', 'network', 'sids']
|
|
SATELLITE_STATUS = ['alive', 'dead', 're-entered']
|
|
TRANSMITTER_STATUS = ['active', 'inactive', 'invalid']
|
|
TRANSMITTER_TYPE = ['Transmitter', 'Transceiver', 'Transponder']
|
|
|
|
|
|
def _name_payload_frame(instance, filename):
|
|
today = now()
|
|
folder = 'payload_frames/{0}/{1}/{2}/'.format(today.year, today.month, today.day)
|
|
ext = 'raw'
|
|
filename = '{0}_{1}.{2}'.format(filename, uuid4().hex, ext)
|
|
return path.join(folder, filename)
|
|
|
|
|
|
def _gen_observer(sender, instance, created, **kwargs):
|
|
post_save.disconnect(_gen_observer, sender=DemodData)
|
|
try:
|
|
qth = gridsquare(instance.lat, instance.lng)
|
|
except Exception:
|
|
instance.observer = 'Unknown'
|
|
else:
|
|
instance.observer = '{0}-{1}'.format(instance.station, qth)
|
|
instance.save()
|
|
post_save.connect(_gen_observer, sender=DemodData)
|
|
|
|
|
|
def _set_is_decoded(sender, instance, **kwargs):
|
|
instance.is_decoded = instance.payload_decoded != ''
|
|
|
|
|
|
class Mode(models.Model):
|
|
name = models.CharField(max_length=10, unique=True)
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
|
|
class Satellite(models.Model):
|
|
"""Model for all the satellites."""
|
|
norad_cat_id = models.PositiveIntegerField()
|
|
name = models.CharField(max_length=45)
|
|
names = models.TextField(blank=True)
|
|
description = models.TextField(blank=True)
|
|
image = models.ImageField(upload_to='satellites', blank=True, help_text='Ideally: 250x250')
|
|
tle1 = models.CharField(max_length=200, blank=True)
|
|
tle2 = models.CharField(max_length=200, blank=True)
|
|
tle_source = models.CharField(max_length=300, blank=True)
|
|
status = models.CharField(
|
|
choices=zip(SATELLITE_STATUS, SATELLITE_STATUS), max_length=10, default='alive'
|
|
)
|
|
decayed = models.DateTimeField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['norad_cat_id']
|
|
|
|
def get_description(self):
|
|
return markdown(self.description)
|
|
|
|
def get_image(self):
|
|
if self.image and hasattr(self.image, 'url'):
|
|
return self.image.url
|
|
else:
|
|
return settings.SATELLITE_DEFAULT_IMAGE
|
|
|
|
@property
|
|
def transmitters(self):
|
|
return Transmitter.objects.filter(satellite=self.id).exclude(status='invalid')
|
|
|
|
@property
|
|
def pending_transmitter_suggestions(self):
|
|
pending = TransmitterSuggestion.objects.filter(satellite=self.id).count()
|
|
return pending
|
|
|
|
@property
|
|
def has_telemetry_data(self):
|
|
has_data = DemodData.objects.filter(satellite=self.id).count()
|
|
return has_data
|
|
|
|
@property
|
|
def has_telemetry_decoders(self):
|
|
has_decoders = Telemetry.objects.filter(satellite=self.id).exclude(decoder='').count()
|
|
return has_decoders
|
|
|
|
def __unicode__(self):
|
|
return '{0} - {1}'.format(self.norad_cat_id, self.name)
|
|
|
|
|
|
class TransmitterEntry(models.Model):
|
|
"""Model for satellite transmitters."""
|
|
uuid = ShortUUIDField(db_index=True)
|
|
description = models.TextField()
|
|
status = models.CharField(
|
|
choices=zip(TRANSMITTER_STATUS, TRANSMITTER_STATUS), max_length=8, default='active'
|
|
)
|
|
type = models.CharField(
|
|
choices=zip(TRANSMITTER_TYPE, TRANSMITTER_TYPE), max_length=11, default='Transmitter'
|
|
)
|
|
uplink_low = models.BigIntegerField(blank=True, null=True)
|
|
uplink_high = models.BigIntegerField(blank=True, null=True)
|
|
uplink_drift = models.IntegerField(blank=True, null=True)
|
|
downlink_low = models.BigIntegerField(blank=True, null=True)
|
|
downlink_high = models.BigIntegerField(blank=True, null=True)
|
|
downlink_drift = models.IntegerField(blank=True, null=True)
|
|
mode = models.ForeignKey(
|
|
Mode, blank=True, null=True, on_delete=models.SET_NULL, related_name='transmitter_entries'
|
|
)
|
|
invert = models.BooleanField(default=False)
|
|
baud = models.FloatField(validators=[MinValueValidator(0)], blank=True, null=True)
|
|
satellite = models.ForeignKey(
|
|
Satellite, null=True, related_name='transmitter_entries', on_delete=models.SET_NULL
|
|
)
|
|
reviewed = models.BooleanField(default=False)
|
|
approved = models.BooleanField(default=False)
|
|
created = models.DateTimeField(default=now)
|
|
citation = models.CharField(max_length=512, default='CITATION NEEDED - https://xkcd.com/285/')
|
|
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
|
|
|
|
class Meta:
|
|
unique_together = ("uuid", "created")
|
|
verbose_name_plural = 'Transmitter entries'
|
|
|
|
def __unicode__(self):
|
|
return self.description
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.id = None
|
|
super(TransmitterEntry, self).save()
|
|
|
|
|
|
class TransmitterSuggestionManager(models.Manager):
|
|
def get_queryset(self):
|
|
return TransmitterEntry.objects.filter(reviewed=False)
|
|
|
|
|
|
class TransmitterSuggestion(TransmitterEntry):
|
|
objects = TransmitterSuggestionManager()
|
|
|
|
class Meta:
|
|
proxy = True
|
|
permissions = (('approve', 'Can approve/reject transmitter suggestions'), )
|
|
|
|
|
|
class TransmitterManager(models.Manager):
|
|
def get_queryset(self):
|
|
subquery = TransmitterEntry.objects.filter(
|
|
reviewed=True, approved=True
|
|
).filter(uuid=OuterRef('uuid')).order_by('-created')
|
|
return super(TransmitterManager, self).get_queryset().filter(
|
|
reviewed=True, approved=True
|
|
).filter(created=Subquery(subquery.values('created')[:1]))
|
|
|
|
|
|
class Transmitter(TransmitterEntry):
|
|
objects = TransmitterManager()
|
|
|
|
class Meta:
|
|
proxy = True
|
|
|
|
|
|
class Telemetry(models.Model):
|
|
"""Model for satellite telemtry decoders."""
|
|
satellite = models.ForeignKey(
|
|
Satellite, null=True, related_name='telemetries', on_delete=models.SET_NULL
|
|
)
|
|
name = models.CharField(max_length=45)
|
|
schema = models.TextField(blank=True)
|
|
decoder = models.CharField(max_length=20, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['satellite__norad_cat_id']
|
|
verbose_name_plural = 'Telemetries'
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
|
|
class DemodData(models.Model):
|
|
"""Model for satellite for observation data."""
|
|
satellite = models.ForeignKey(
|
|
Satellite, null=True, related_name='telemetry_data', on_delete=models.SET_NULL
|
|
)
|
|
transmitter = models.ForeignKey(
|
|
TransmitterEntry, null=True, blank=True, on_delete=models.SET_NULL
|
|
)
|
|
app_source = models.CharField(
|
|
choices=zip(DATA_SOURCES, DATA_SOURCES), max_length=7, default='sids'
|
|
)
|
|
data_id = models.PositiveIntegerField(blank=True, null=True)
|
|
payload_frame = models.FileField(upload_to=_name_payload_frame, blank=True, null=True)
|
|
payload_decoded = models.TextField(blank=True)
|
|
payload_telemetry = models.ForeignKey(
|
|
Telemetry, null=True, blank=True, on_delete=models.SET_NULL
|
|
)
|
|
station = models.CharField(max_length=45, default='Unknown')
|
|
observer = models.CharField(max_length=60, blank=True)
|
|
lat = models.FloatField(validators=[MaxValueValidator(90), MinValueValidator(-90)], default=0)
|
|
lng = models.FloatField(
|
|
validators=[MaxValueValidator(180), MinValueValidator(-180)], default=0
|
|
)
|
|
is_decoded = models.BooleanField(default=False, db_index=True)
|
|
timestamp = models.DateTimeField(null=True)
|
|
|
|
class Meta:
|
|
ordering = ['-timestamp']
|
|
|
|
def __unicode__(self):
|
|
return 'data-for-{0}'.format(self.satellite.norad_cat_id)
|
|
|
|
def display_decoded(self):
|
|
try:
|
|
json.dumps(self.payload_decoded)
|
|
except Exception:
|
|
'{}'
|
|
|
|
def display_frame(self):
|
|
try:
|
|
with open(self.payload_frame.path) as (fp):
|
|
return fp.read()
|
|
except IOError as err:
|
|
logger.error(
|
|
err, exc_info=True, extra={
|
|
'payload frame path': self.payload_frame.path,
|
|
}
|
|
)
|
|
return None
|
|
|
|
|
|
post_save.connect(_gen_observer, sender=DemodData)
|
|
pre_save.connect(_set_is_decoded, sender=DemodData)
|