class RTL433MsgGroup(ExportedState): implements(ITelemetryObject) 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, callback): """Overrides ExportedState.""" super(RTL433MsgGroup, self).state_def(callback) for cell in self.__cells.itervalues(): callback(cell) # not exported def receive(self, message_wrapper): """Implements ITelemetryObject.""" self.__last_heard_time = message_wrapper.receive_time shape_changed = False for k, v in message_wrapper.message.iteritems(): if _message_field_is_id.get(k, False) or k == u'time': continue if k not in self.__cells: shape_changed = True self.__cells[k] = LooseCell(key=k, value=None, type=object, writable=False, persists=False, label=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=Timestamp(), changes='explicit', label='Last heard') def get_last_heard_time(self): return self.__last_heard_time
class APRSStation(ExportedState): implements(IAPRSStation, ITelemetryObject) def __init__(self, object_id): self.__last_heard_time = None self.__address = object_id self.__track = empty_track self.__status = u'' self.__symbol = None 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) 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=Timestamp()) def get_last_heard_time(self): return self.__last_heard_time @exported_value(type=unicode) def get_address(self): return self.__address @exported_value(type=Track) def get_track(self): return self.__track @exported_value(type=unicode) def get_symbol(self): """APRS symbol table identifier and symbol.""" return self.__symbol @exported_value(type=unicode) def get_status(self): """String status text.""" return self.__status @exported_value(type=unicode) def get_last_comment(self): return self.__last_comment @exported_value(type=Notice(always_visible=False)) def get_last_parse_error(self): return self.__last_parse_error
# TODO: We should probably take larger-than-current day numbers as the previous month, and similar for hours just before midnight in 'h' format try: if kind == 'h': absolute_time = datetime.utcfromtimestamp(receive_time).replace( hour=n1, minute=n2, second=n3) elif kind == 'z': absolute_time = datetime.utcfromtimestamp(receive_time).replace( day=n1, hour=n2, minute=n3, second=0, microsecond=0) else: # kind == '/' absolute_time = datetime.fromtimestamp(receive_time).replace( day=n1, hour=n2, minute=n3, second=0, microsecond=0) except ValueError, e: errors.append('DHM/HMS timestamp invalid: %s' % (e.message, )) return facts.append(Timestamp(absolute_time)) def _parse_angle(angle_str): # TODO return imprecision information # TODO old notes say "." is allowed as imprecision, check match = re.match(r'^(\d{1,3})([\d ]{2}\.[\d ]{2})([NESW])$', angle_str) if not match: return None else: degrees, minutes, direction = match.groups() minutes = minutes.replace(' ', '0') if direction == 'S' or direction == 'W': sign = -1 else: sign = 1
class Aircraft(ExportedState): implements(IAircraft, ITelemetryObject) 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=Timestamp(), 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', 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=unicode, 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