class RTL433MsgGroup(ExportedState): def __init__(self, object_id): """Implements ITelemetryObject.""" self.__cells = {} self.__last_heard_time = None def state_is_dynamic(self): """Overrides ExportedState.""" return True def state_def(self): """Overrides ExportedState.""" for d in super(RTL433MsgGroup, self).state_def(): yield d for d in six.iteritems(self.__cells): yield d # not exported def receive(self, message_wrapper): """Implements ITelemetryObject.""" self.__last_heard_time = message_wrapper.receive_time shape_changed = False for k, v in six.iteritems(message_wrapper.message): if k in _id_component_fields or k in _ignored_fields: continue if k not in self.__cells: shape_changed = True self.__cells[k] = LooseCell(value=None, type=object, writable=False, persists=False, label=k, sort_key='1' + k) self.__cells[k].set_internal(v) self.state_changed() if shape_changed: self.state_shape_changed() def is_interesting(self): """Implements ITelemetryObject.""" return True def get_object_expiry(self): """implement ITelemetryObject""" return self.__last_heard_time + drop_unheard_timeout_seconds @exported_value(type=TimestampT(), changes='explicit', label='Last heard', sort_key='9heard') def get_last_heard_time(self): return self.__last_heard_time
class Aircraft(ExportedState): def __init__(self, object_id): """Implements ITelemetryObject. object_id is the hex formatted address.""" self.__last_heard_time = None self.__track = empty_track self.__call = None self.__ident = None self.__aircraft_type = None # not exported def receive(self, message_wrapper): message = message_wrapper.message cpr_decoder = message_wrapper.cpr_decoder receive_time = message_wrapper.receive_time self.__last_heard_time = receive_time # Unfortunately, gr-air-modes doesn't provide a function to implement this gunk -- imitating its output_flightgear code which data = message.data t = data.get_type() if t == 0: self.__track = self.__track._replace(altitude=TelemetryItem( air_modes.decode_alt(data['ac'], True) * _METERS_PER_FEET, receive_time)) # TODO more info available here elif t == 4: self.__track = self.__track._replace(altitude=TelemetryItem( air_modes.decode_alt(data['ac'], True) * _METERS_PER_FEET, receive_time)) # TODO more info available here elif t == 5: self.__ident = air_modes.decode_id(data['id']) # TODO more info available here elif t == 17: # ADS-B bdsreg = data['me'].get_type() if bdsreg == 0x05: # TODO use unused info (altitude_feet, latitude, longitude, _range, _bearing) = air_modes.parseBDS05(data, cpr_decoder) self.__track = self.__track._replace( altitude=TelemetryItem(altitude_feet * _METERS_PER_FEET, receive_time), latitude=TelemetryItem(latitude, receive_time), longitude=TelemetryItem(longitude, receive_time), ) elif bdsreg == 0x06: # TODO use unused info (_ground_track, latitude, longitude, _range, _bearing) = air_modes.parseBDS06(data, cpr_decoder) self.__track = self.__track._replace( latitude=TelemetryItem(latitude, receive_time), longitude=TelemetryItem(longitude, receive_time), ) elif bdsreg == 0x08: (self.__call, self.__aircraft_type) = air_modes.parseBDS08(data) elif bdsreg == 0x09: subtype = data['bds09'].get_type() if subtype == 0: (velocity, heading, vertical_speed, _turn_rate) = air_modes.parseBDS09_0(data) # TODO: note we're stuffing the heading in as track angle. Is there something better to do? self.__track = self.__track._replace( h_speed=TelemetryItem( velocity * _KNOTS_TO_METERS_PER_SECOND, receive_time), heading=TelemetryItem(heading, receive_time), track_angle=TelemetryItem(heading, receive_time), v_speed=TelemetryItem(vertical_speed, receive_time), # TODO add turn rate ) elif subtype == 1: (velocity, heading, vertical_speed) = air_modes.parseBDS09_1(data) self.__track = self.__track._replace( h_speed=TelemetryItem( velocity * _KNOTS_TO_METERS_PER_SECOND, receive_time), heading=TelemetryItem(heading, receive_time), track_angle=TelemetryItem(heading, receive_time), v_speed=TelemetryItem(vertical_speed, receive_time), # TODO reset turn rate? ) else: # TODO report pass else: # TODO report pass else: # TODO report pass self.state_changed() def is_interesting(self): """ Implements ITelemetryObject. Does this aircraft have enough information to be worth mentioning? """ # TODO: Loosen this rule once we have more efficient state transfer (no polling) and better UI for viewing them on the client. return \ self.__track.latitude.value is not None or \ self.__track.longitude.value is not None or \ self.__call is not None or \ self.__aircraft_type is not None def get_object_expiry(self): """implement ITelemetryObject""" return self.__last_heard_time + drop_unheard_timeout_seconds @exported_value(type=TimestampT(), changes='explicit', sort_key='100', label='Last heard') def get_last_heard_time(self): return self.__last_heard_time @exported_value(type=six.text_type, changes='explicit', sort_key='020', label='Call') # TODO naming may be wrong def get_call(self): return self.__call @exported_value(type=int, changes='explicit', sort_key='050', label='Ident') # TODO naming may be wrong def get_ident(self): return self.__ident @exported_value(type=six.text_type, changes='explicit', sort_key='020', label='Aircraft type') def get_aircraft_type(self): return self.__aircraft_type @exported_value(type=Track, changes='explicit', sort_key='010', label='') def get_track(self): return self.__track
class APRSStation(ExportedState): def __init__(self, object_id): self.__last_heard_time = None self.__address = object_id self.__track = empty_track self.__status = u'' self.__symbol = u'' self.__last_comment = u'' self.__last_parse_error = u'' def receive(self, message): """implement ITelemetryObject""" self.__last_heard_time = message.receive_time for fact in message.facts: if isinstance(fact, KillObject): # Kill by pretending the object is ancient. self.__last_heard_time = 0 if isinstance(fact, Position): self.__track = self.__track._replace( latitude=TelemetryItem(fact.latitude, message.receive_time), longitude=TelemetryItem(fact.longitude, message.receive_time), ) if isinstance(fact, Altitude): conversion = _FEET_TO_METERS if fact.feet_not_meters else 1 self.__track = self.__track._replace(altitude=TelemetryItem( fact.value * conversion, message.receive_time), ) if isinstance(fact, Velocity): self.__track = self.__track._replace( h_speed=TelemetryItem( fact.speed_knots * _KNOTS_TO_METERS_PER_SECOND, message.receive_time), track_angle=TelemetryItem(fact.course_degrees, message.receive_time), ) elif isinstance(fact, Status): # TODO: Empirically, not always ASCII. Move this implicit decoding off into parse stages. self.__status = unicode(fact.text) elif isinstance(fact, Symbol): self.__symbol = unicode(fact.id) else: # TODO: Warn somewhere in this case (recognized by parser but not here) pass self.__last_comment = unicode(message.comment) if len(message.errors) > 0: self.__last_parse_error = '; '.join(message.errors) self.state_changed() def is_interesting(self): """implement ITelemetryObject""" return True def get_object_expiry(self): """implement ITelemetryObject""" return self.__last_heard_time + drop_unheard_timeout_seconds @exported_value(type=TimestampT(), changes='explicit', sort_key='100', label='Last heard') def get_last_heard_time(self): return self.__last_heard_time @exported_value(type=unicode, changes='explicit', label='Address/object ID') def get_address(self): return self.__address @exported_value(type=Track, changes='explicit', sort_key='010', label='') def get_track(self): return self.__track @exported_value(type=unicode, changes='explicit', sort_key='020', label='Symbol') def get_symbol(self): """APRS symbol table identifier and symbol.""" return self.__symbol @exported_value(type=unicode, changes='explicit', sort_key='080', label='Status') def get_status(self): """String status text.""" return self.__status @exported_value(type=unicode, changes='explicit', sort_key='090', label='Last message comment') def get_last_comment(self): return self.__last_comment @exported_value(type=NoticeT(always_visible=False), sort_key='000', changes='explicit') def get_last_parse_error(self): return self.__last_parse_error
class WSPRStation(ExportedState): __last_heard = 0 __snr = None __frequency = None __call = None __grid = None __txpower = None def __init__(self, object_id): pass def receive(self, message): self.__last_heard = message.time self.__snr = message.snr self.__frequency = message.frequency self.__call = message.call self.__grid = message.grid self.__txpower = message.txpower self.state_changed() def is_interesting(self): """Every WSPR message is about as interesting as another, I suppose.""" return True def get_object_expiry(self): return self.__last_heard + 30 * MINUTES @exported_value(type=TimestampT(), changes='explicit', label='Last heard') def get_last_heard(self): return self.__last_heard @exported_value(type=QuantityT(units.dB), changes='explicit', label='SNR') def get_snr(self): return self.__snr or -999 @exported_value(type=QuantityT(units.MHz), changes='explicit', label='Frequency') def get_frequency(self): return self.__frequency or 0 @exported_value(type=unicode, changes='explicit', label='Call') def get_call(self): return self.__call or '' @exported_value(type=unicode, changes='explicit', label='Grid') def get_grid(self): return self.__grid or '' @exported_value(type=QuantityT(units.dBm), changes='explicit', label='Tx Power') def get_txpower(self): return self.__txpower or 0 @exported_value(type=Track, changes='explicit', label='Track') def get_track(self): if self.__grid: latitude, longitude = grid_to_lat_long(self.__grid) track = Track(latitude=TelemetryItem(latitude, self.__last_heard), longitude=TelemetryItem(longitude, self.__last_heard)) return track else: return empty_track