Example #1
0
    def __init__(self, uuid, user, connection, clock, position_llh, privacy,
                 connection_info):
        self.uuid = uuid
        self.user = user
        self.connection = connection
        self.clock = clock
        self.last_clock_reset = time.monotonic()
        self.clock_reset_counter = 0
        self.position_llh = position_llh
        self.position = geodesy.llh2ecef(position_llh)
        self.privacy = privacy
        self.connection_info = connection_info
        self.dead = False

        self.sync_count = 0
        self.sync_peers = 0  # number of peers hopefully updated live
        self.peer_count = 0  # only updated when dumping state
        self.last_rate_report = None
        self.tracking = set()
        self.adsb_seen = set()
        self.sync_interest = set()
        self.mlat_interest = set()
        self.requested = set()
        self.offX = 0.016 * random.randrange(-1, 2, 2)
        self.offY = 0.016 * random.randrange(-1, 2, 2)

        self.distance = {}

        # Receivers with bad_syncs>0 are not used to calculate positions
        self.bad_syncs = 0
        self.sync_range_exceeded = 0
Example #2
0
    def receiver_location_update(self, receiver, position_llh):
        """Note that a given receiver has moved."""
        print("Receiver {r} Position Update {p}".format(r=receiver.user,
                                                        p=position_llh))
        receiver.position_llh = position_llh
        receiver.position = geodesy.llh2ecef(position_llh)

        self._compute_interstation_distances(receiver)
Example #3
0
    def __init__(self, uid, user, connection, clock_type, position_llh,
                 privacy, connection_info, uuid, coordinator, clock_tracker):
        self.uid = uid
        self.uuid = uuid
        self.user = user
        self.connection = connection
        self.coordinator = coordinator
        self.clock_tracker = clock_tracker
        self.clock = clocktrack.make_clock(clock_type)
        self.epoch = None
        if clock_type == 'radarcape_gps':
            self.epoch = 'gps_midnight'
        self.last_clock_reset = time.time()
        self.clock_reset_counter = 0
        self.position_llh = position_llh
        self.position = geodesy.llh2ecef(position_llh)
        self.privacy = privacy
        self.connection_info = connection_info
        self.dead = False
        self.connectedSince = time.time()

        self.num_outliers = 0
        self.num_syncs = 0

        self.outlier_percent_rolling = 0

        self.sync_peers = array.array(
            'i', [0, 0, 0, 0, 0])  # number of peers per distance category
        self.peer_count = 0  # only updated when dumping state
        self.last_rate_report = None
        self.tracking = set()
        self.adsb_seen = set()
        self.sync_interest = set()
        self.mlat_interest = set()
        self.requested = set()
        self.offX = 1 / 20 * random.random()
        self.offY = 1 / 20 * random.random()

        self.distance = {}

        # timestamp this receiver last synced with the result being a valid clock pair
        self.last_sync = 0

        # Receivers with bad_syncs>0 are not used to calculate positions
        self.bad_syncs = 0
        self.sync_range_exceeded = 0

        self.recent_pair_jumps = 0

        self.focus = False
Example #4
0
    def __init__(self, uuid, user, connection, clock, position_llh, privacy, connection_info):
        self.uuid = uuid
        self.user = user
        self.connection = connection
        self.clock = clock
        self.position_llh = position_llh
        self.position = geodesy.llh2ecef(position_llh)
        self.privacy = privacy
        self.connection_info = connection_info
        self.dead = False

        self.sync_count = 0
        self.last_rate_report = None
        self.tracking = set()
        self.sync_interest = set()
        self.mlat_interest = set()
        self.requested = set()

        self.distance = {}
Example #5
0
    def __init__(self, uuid, user, connection, clock, position_llh, privacy,
                 connection_info):
        self.uuid = uuid
        self.user = user
        self.connection = connection
        self.clock = clock
        self.position_llh = position_llh
        self.position = geodesy.llh2ecef(position_llh)
        self.privacy = privacy
        self.connection_info = connection_info
        self.dead = False

        self.sync_count = 0
        self.last_rate_report = None
        self.tracking = set()
        self.sync_interest = set()
        self.mlat_interest = set()
        self.requested = set()

        self.distance = {}
Example #6
0
    def __init__(self, uuid, user, connection, clock, position_llh, privacy, connection_info):
        self.uuid = uuid
        self.user = user
        self.connection = connection
        self.clock = clock
        self.position_llh = position_llh
        self.position = geodesy.llh2ecef(position_llh)
        self.privacy = privacy
        self.connection_info = connection_info
        self.dead = False

        self.sync_count = 0
        self.peer_count = 0 # only updated when dumping state
        self.last_rate_report = None
        self.tracking = set()
        self.sync_interest = set()
        self.mlat_interest = set()
        self.requested = set()

        self.distance = {}

        # Receivers with bad_syncs>0 are not used to calculate positions
        self.bad_syncs = 0
Example #7
0
    def receiver_sync(self, receiver,
                      even_time, odd_time,
                      even_message, odd_message):
        """
        Called by the coordinator to handle a sync message from a receiver.

        Looks for a suitable existing sync point and, if there is one, does
        synchronization between this receiver and the existing receivers
        associated with the sync point.

        Otherwise, validates the message pair and, if it is suitable, creates a
        new sync point for it.

        receiver: the receiver reporting the sync message
        even_message: a DF17 airborne position message with F=0
        odd_message: a DF17 airborne position message with F=1
        even_time: the time of arrival of even_message, as seen by receiver.clock
        odd_time: the time of arrival of odd_message, as seen by receiver.clock
        """

        # Do sanity checks.

        # Messages must be within 5 seconds of each other.
        if abs(even_time - odd_time) / receiver.clock.freq > 5.0:
            return

        # compute key and interval
        if even_time < odd_time:
            tA = even_time
            tB = odd_time
            key = (even_message, odd_message)
        else:
            tA = odd_time
            tB = even_time
            key = (odd_message, even_message)

        interval = (tB - tA) / receiver.clock.freq

        # do we have a suitable existing match?
        syncpointlist = self.sync_points.get(key)
        if syncpointlist:
            for candidate in syncpointlist:
                if abs(candidate.interval - interval) < 1e-3:
                    # interval matches within 1ms, close enough.
                    self._add_to_existing_syncpoint(candidate, receiver, tA, tB)
                    return

        # No existing match. Validate the messages and maybe create a new sync point

        # basic validity
        even_message = modes.message.decode(even_message)
        if ((not even_message or
             even_message.DF != 17 or
             not even_message.crc_ok or
             even_message.estype != modes.message.ESType.airborne_position or
             even_message.F)):
            return

        odd_message = modes.message.decode(odd_message)
        if ((not odd_message or
             odd_message.DF != 17 or
             not odd_message.crc_ok or
             odd_message.estype != modes.message.ESType.airborne_position or
             not odd_message.F)):
            return

        if even_message.address != odd_message.address:
            return

        # quality checks
        if even_message.nuc < 6 or even_message.altitude is None:
            return

        if odd_message.nuc < 6 or odd_message.altitude is None:
            return

        if abs(even_message.altitude - odd_message.altitude) > 5000:
            return

        # find global positions
        try:
            even_lat, even_lon, odd_lat, odd_lon = modes.cpr.decode(even_message.LAT,
                                                                    even_message.LON,
                                                                    odd_message.LAT,
                                                                    odd_message.LON)
        except ValueError:
            # CPR failed
            return

        # convert to ECEF, do range checks
        even_ecef = geodesy.llh2ecef((even_lat,
                                      even_lon,
                                      even_message.altitude * constants.FTOM))
        if geodesy.ecef_distance(even_ecef, receiver.position) > config.MAX_RANGE:
            logging.info("{a:06X}: receiver range check (even) failed".format(a=even_message.address))
            return

        odd_ecef = geodesy.llh2ecef((odd_lat,
                                     odd_lon,
                                     odd_message.altitude * constants.FTOM))
        if geodesy.ecef_distance(odd_ecef, receiver.position) > config.MAX_RANGE:
            logging.info("{a:06X}: receiver range check (odd) failed".format(a=odd_message.address))
            return

        if geodesy.ecef_distance(even_ecef, odd_ecef) > config.MAX_INTERMESSAGE_RANGE:
            logging.info("{a:06X}: intermessage range check failed".format(a=even_message.address))
            return

        # valid. Create a new sync point.
        if even_time < odd_time:
            syncpoint = SyncPoint(even_message.address, even_ecef, odd_ecef, interval)
        else:
            syncpoint = SyncPoint(even_message.address, odd_ecef, even_ecef, interval)

        syncpoint.receivers.append([receiver, tA, tB, False])
        if not syncpointlist:
            syncpointlist = self.sync_points[key] = []
        syncpointlist.append(syncpoint)

        # schedule cleanup of the syncpoint after 2 seconds -
        # we should have seen all copies of those messages by
        # then.
        asyncio.get_event_loop().call_later(
            2.0,
            functools.partial(self._cleanup_syncpoint,
                              key=key,
                              syncpoint=syncpoint))
Example #8
0
    def receiver_location_update(self, receiver, position_llh):
        """Note that a given receiver has moved."""
        receiver.position_llh = position_llh
        receiver.position = geodesy.llh2ecef(position_llh)

        self._compute_interstation_distances(receiver)
Example #9
0
    def _resolve(self, group):
        del self.pending[group.message]


        # less than 3 messages -> no go
        if len(group.copies) < 3:
            return

        decoded = modes_cython.message.decode(group.message)

        if not decoded or not decoded.address:
            return

        ac = self.tracker.aircraft.get(decoded.address)
        if not ac:
            return
        now = time.time()
        ac.seen = now

        ac.mlat_message_count += 1

        self.coordinator.stats_valid_groups += 1

        if not ac.allow_mlat:
            glogger.info("not doing mlat for {0:06x}, wrong partition!".format(ac.icao))
            return

        # When we've seen a few copies of the same message, it's
        # probably correct. Update the tracker with newly seen
        # altitudes, squawks, callsigns.
        if decoded.altitude is not None and decoded.altitude > -1500 and decoded.altitude < 75000:
            if (not ac.last_altitude_time
                    or (group.first_seen > ac.last_altitude_time
                        and (group.first_seen - ac.last_altitude_time > 15 or abs(ac.altitude - decoded.altitude) < 4000)
                        )
                    ):
                ac.altitude = decoded.altitude
                ac.last_altitude_time = group.first_seen

                new_hist = []
                for ts, old_alt in ac.alt_history:
                    if group.first_seen - ts < 20.0:
                        new_hist.append((ts, old_alt))

                ac.alt_history = new_hist
                ac.alt_history.append((group.first_seen, decoded.altitude))

                ts_diff = group.first_seen - new_hist[0][0]
                if ts_diff > 10:
                    # fpm
                    new_vrate = (decoded.altitude - new_hist[0][1]) / (ts_diff / 60.0)
                    if ac.vrate and group.first_seen - ac.vrate_time < 15:
                        ac.vrate = int(ac.vrate + 0.3 * (new_vrate - ac.vrate))
                    else:
                        ac.vrate = int(new_vrate)
                    ac.vrate_time = group.first_seen

        if decoded.squawk is not None:
            ac.squawk = decoded.squawk

        if decoded.callsign is not None:
            ac.callsign = decoded.callsign

        if now - ac.last_resolve_attempt < config.RESOLVE_INTERVAL:
            return
        ac.last_resolve_attempt = now

        # find old result, if present
        if ac.last_result_position is None or (group.first_seen - ac.last_result_time) > 120:
            last_result_position = None
            last_result_var = 1e9
            last_result_dof = 0
            last_result_time = group.first_seen - 120
        else:
            last_result_position = ac.last_result_position
            last_result_var = ac.last_result_var
            last_result_dof = ac.last_result_dof
            last_result_time = ac.last_result_time

        elapsed = group.first_seen - last_result_time

        if elapsed < 0:
            elapsed = 0

        if elapsed < config.RESOLVE_BACKOFF:
            return

        # find altitude
        if (
                ac.altitude is None
                or ac.altitude < config.MIN_ALT
                or ac.altitude > config.MAX_ALT
                or group.first_seen > ac.last_altitude_time + 45
            ):
            altitude = None
            altitude_dof = 0
        else:
            altitude = ac.altitude * constants.FTOM
            altitude_dof = 1

        len_copies = len(group.copies)
        max_dof = len_copies + altitude_dof - 4

        if max_dof < 0:
            return
        if elapsed < 2 * config.RESOLVE_BACKOFF and max_dof < last_result_dof - elapsed + 0.5:
            return

        # construct a map of receiver -> list of timestamps
        timestamp_map = {}
        for receiver, timestamp, utc in group.copies:
            timestamp_map.setdefault(receiver, []).append((timestamp, utc))

        # check for minimum needed receivers
        dof = len(timestamp_map) + altitude_dof - 4

        if dof < 0:
            return
        if elapsed < 2 * config.RESOLVE_BACKOFF and dof < last_result_dof - elapsed + 0.5:
            return

        self.coordinator.stats_normalize += 1

        # normalize timestamps. This returns a list of timestamp maps;
        # within each map, the timestamp values are comparable to each other.
        try:
            components = clocktrack.normalize2(clocktracker=self.clock_tracker,
                                             timestamp_map=timestamp_map)
        except Exception as e:
            traceback.print_exc()
            return

        # cluster timestamps into clusters that are probably copies of the
        # same transmission.
        clusters = []
        min_component_size = 4 - altitude_dof
        for component in components:
            if len(component) >= min_component_size:  # don't bother with orphan components at all
                clusters.extend(_cluster_timestamps(component, min_component_size))

        if not clusters:
            return

        # start from the most recent, largest, cluster
        result = None
        error = None
        clusters.sort(key=lambda x: (x[0], x[1]))
        while clusters and not result:
            distinct, cluster_utc, cluster = clusters.pop()

            # accept fewer receivers after 10s
            # accept more receivers immediately

            elapsed = cluster_utc - last_result_time
            dof = distinct + altitude_dof - 4

            if elapsed < 2 and dof < last_result_dof - elapsed + 0.5:
                return

            # assume 250ft accuracy at the time it is reported
            # (this bundles up both the measurement error, and
            # that we don't adjust for local pressure)
            #
            # Then degrade the accuracy over time at ~4000fpm
            if decoded.altitude is not None:
                altitude_error = 250 * constants.FTOM
            elif altitude is not None:
                altitude_error = (250 + (cluster_utc - ac.last_altitude_time) * 70) * constants.FTOM
            else:
                altitude_error = None

            cluster.sort(key=operator.itemgetter(1))  # sort by increasing timestamp (todo: just assume descending..)

            if elapsed > 30 and dof == 0:
                continue

            if elapsed < 60:
                initial_guess = last_result_position
            else:
                initial_guess = cluster[0][0].position

            self.coordinator.stats_solve_attempt += 1
            r = solver.solve(cluster, altitude, altitude_error, initial_guess)

            if r:
                # estimate the error
                ecef, ecef_cov = r
                max_error = 10e3 # 10 km
                if ecef_cov is not None:
                    var_est = numpy.trace(ecef_cov)
                else:
                    # this result is suspect
                    var_est = max_error * max_error
                    #glogger.warn('{a:06X} {e:7.3f} '.format(a=decoded.address, e=999.999) + str([line[0].user for line in cluster]))
                    # don't use this
                    continue

                error = int(math.sqrt(abs(var_est)))

                if False and elapsed > 30 and error < 1e9:
                    lat, lon, alt = geodesy.ecef2llh(ecef)
                    ecef, ecef_cov = r
                    glogger.warn('{a:06X} {e:8.1f} {lat:7.3f},{lon:7.3f},{alt:5.0f} '.format(a=decoded.address, e=error/1000, lat=lat, lon=lon, alt=alt) + str([line[0].user for line in cluster]))

                if error > max_error:
                    continue


                self.coordinator.stats_solve_success += 1

                # the higher the accuracy, the higher the freqency of positions that is output
                if elapsed / 20 < error / max_error:
                    continue

                self.coordinator.stats_solve_used += 1

                #if elapsed < 10.0 and var_est > last_result_var * 2.25:
                #    # much less accurate than a recent-ish position
                #    continue

                # accept it
                result = r

        if not result:
            return

        ecef, ecef_cov = result
        ac.last_result_position = ecef
        ac.last_result_var = var_est
        ac.last_result_dof = dof
        ac.last_result_time = cluster_utc
        ac.mlat_result_count += 1

        if altitude is not None:
            lat, lon, _ = geodesy.ecef2llh(ecef)
            # replace ecef altitude with reported altitude
            ecef = geodesy.llh2ecef([lat, lon, altitude])
            if ac.kalman.update(cluster_utc, cluster, altitude, altitude_error, ecef, ecef_cov, distinct, dof):
                ac.mlat_kalman_count += 1
        else:
            _, _, solved_alt = geodesy.ecef2llh(ecef)
            #glogger.info("{addr:06x} solved altitude={solved_alt:.0f}ft with dof={dof}".format(
            #    addr=decoded.address,
            #    solved_alt=solved_alt*constants.MTOF,
            #    dof=dof))
            if ac.kalman.update(cluster_utc, cluster, solved_alt, 4000 / math.sqrt(dof + 1), ecef, ecef_cov, distinct, dof):
                ac.mlat_kalman_count += 1

        for handler in self.coordinator.output_handlers:
            handler(cluster_utc, decoded.address,
                    ecef, ecef_cov,
                    [receiver for receiver, timestamp, error in cluster], distinct, dof,
                    ac.kalman, error)

        # forward result to all receivers that received the raw message the result is based on
        self.coordinator.forward_results(cluster_utc, decoded.address,
                ecef, ecef_cov,
                list(group.receivers), distinct, dof,
                ac.kalman, error)

        if self.pseudorange_file:
            cluster_state = []
            t0 = cluster[0][1]
            for receiver, timestamp, variance in cluster:
                cluster_state.append([round(receiver.position[0], 0),
                                      round(receiver.position[1], 0),
                                      round(receiver.position[2], 0),
                                      round((timestamp-t0)*1e6, 1),
                                      round(variance*1e12, 2)])

            state = {'icao': '{a:06x}'.format(a=decoded.address),
                     'time': round(cluster_utc, 3),
                     'ecef': [round(ecef[0], 0),
                              round(ecef[1], 0),
                              round(ecef[2], 0)],
                     'distinct': distinct,
                     'dof': dof,
                     'cluster': cluster_state}

            if ecef_cov is not None:
                state['ecef_cov'] = [round(ecef_cov[0, 0], 0),
                                     round(ecef_cov[0, 1], 0),
                                     round(ecef_cov[0, 2], 0),
                                     round(ecef_cov[1, 0], 0),
                                     round(ecef_cov[1, 1], 0),
                                     round(ecef_cov[1, 2], 0),
                                     round(ecef_cov[2, 0], 0),
                                     round(ecef_cov[2, 1], 0),
                                     round(ecef_cov[2, 2], 0)]

            if altitude is not None:
                state['altitude'] = round(altitude, 0)
                state['altitude_error'] = round(altitude_error, 0)

            ujson.dump(state, self.pseudorange_file)
            self.pseudorange_file.write('\n')
Example #10
0
def solve(measurements, altitude, altitude_error, initial_guess):
    """Given a set of receive timestamps, multilaterate the position of the transmitter.

    measurements: a list of (receiver, timestamp, error) tuples. Should be sorted by timestamp.
      receiver.position should be the ECEF position of the receiver
      timestamp should be a reception time in seconds (with an arbitrary epoch)
      variance should be the estimated variance of timestamp
    altitude: the reported altitude of the transmitter in _meters_, or None
    altitude_error: the estimated error in altitude in meters, or None
    initial_guess: an ECEF position to start the solver from

    Returns None on failure, or (ecef, ecef_cov) on success, with:

    ecef: the multilaterated ECEF position of the transmitter
    ecef_cov: an estimate of the covariance matrix of ecef
    """

    if len(measurements) + (0 if altitude is None else 1) < 4:
        raise ValueError('Not enough measurements available')

    glat, glng, galt = geodesy.ecef2llh(initial_guess)
    if galt < config.MIN_ALT:
        galt = -galt
        initial_guess = geodesy.llh2ecef([glat, glng, galt])
    if galt > config.MAX_ALT:
        galt = config.MAX_ALT
        initial_guess = geodesy.llh2ecef([glat, glng, galt])

    base_timestamp = measurements[0][1]
    pseudorange_data = [(receiver.position,
                         (timestamp - base_timestamp) * constants.Cair,
                         math.sqrt(variance) * constants.Cair)
                        for receiver, timestamp, variance in measurements]
    x_guess = [initial_guess[0], initial_guess[1], initial_guess[2], 0.0]
    x_est, cov_x, infodict, mesg, ler = scipy.optimize.leastsq(
        _residuals,
        x_guess,
        args=(pseudorange_data, altitude, altitude_error),
        full_output=True,
        maxfev=config.SOLVER_MAXFEV)

    if ler in (1, 2, 3, 4):
        #glogger.info("solver success: {0} {1}".format(ler, mesg))

        # Solver found a result. Validate that it makes
        # some sort of physical sense.
        (*position_est, offset_est) = x_est

        if offset_est < 0 or offset_est > config.MAX_RANGE:
            #glogger.info("solver: bad offset: {0}".formaT(offset_est))
            # implausible range offset to closest receiver
            return None

        for receiver, timestamp, variance in measurements:
            d = geodesy.ecef_distance(receiver.position, position_est)
            if d > config.MAX_RANGE:
                # too far from this receiver
                #glogger.info("solver: bad range: {0}".format(d))
                return None

        if cov_x is None:
            return position_est, None
        else:
            return position_est, cov_x[0:3, 0:3]

    else:
        # Solver failed
        #glogger.info("solver: failed: {0} {1}".format(ler, mesg))
        return None
Example #11
0
    def receiver_sync(self, receiver,
                      even_time, odd_time,
                      even_message, odd_message):
        """
        Called by the coordinator to handle a sync message from a receiver.

        Looks for a suitable existing sync point and, if there is one, does
        synchronization between this receiver and the existing receivers
        associated with the sync point.

        Otherwise, validates the message pair and, if it is suitable, creates a
        new sync point for it.

        receiver: the receiver reporting the sync message
        even_message: a DF17 airborne position message with F=0
        odd_message: a DF17 airborne position message with F=1
        even_time: the time of arrival of even_message, as seen by receiver.clock
        odd_time: the time of arrival of odd_message, as seen by receiver.clock
        """

        # Do sanity checks.

        # Messages must be within 5 seconds of each other.
        if abs(even_time - odd_time) / receiver.clock.freq > 5.0:
            return

        # compute key and interval
        if even_time < odd_time:
            tA = even_time
            tB = odd_time
            key = (even_message, odd_message)
        else:
            tA = odd_time
            tB = even_time
            key = (odd_message, even_message)

        interval = (tB - tA) / receiver.clock.freq

        # do we have a suitable existing match?
        syncpointlist = self.sync_points.get(key)
        if syncpointlist:
            for candidate in syncpointlist:
                if abs(candidate.interval - interval) < 1e-3:
                    # interval matches within 1ms, close enough.
                    self._add_to_existing_syncpoint(candidate, receiver, tA, tB)
                    return

        # No existing match. Validate the messages and maybe create a new sync point

        # basic validity
        even_message = modes.message.decode(even_message)
        if ((not even_message or
             even_message.DF != 17 or
             not even_message.crc_ok or
             even_message.estype != modes.message.ESType.airborne_position or
             even_message.F)):
            return

        odd_message = modes.message.decode(odd_message)
        if ((not odd_message or
             odd_message.DF != 17 or
             not odd_message.crc_ok or
             odd_message.estype != modes.message.ESType.airborne_position or
             not odd_message.F)):
            return

        if even_message.address != odd_message.address:
            return

        # quality checks
        if even_message.nuc < 6 or even_message.altitude is None:
            return

        if odd_message.nuc < 6 or odd_message.altitude is None:
            return

        if abs(even_message.altitude - odd_message.altitude) > 5000:
            return

        # find global positions
        try:
            even_lat, even_lon, odd_lat, odd_lon = modes.cpr.decode(even_message.LAT,
                                                                    even_message.LON,
                                                                    odd_message.LAT,
                                                                    odd_message.LON)
        except ValueError:
            # CPR failed
            return

        # convert to ECEF, do range checks
        even_ecef = geodesy.llh2ecef((even_lat,
                                      even_lon,
                                      even_message.altitude * constants.FTOM))
        if geodesy.ecef_distance(even_ecef, receiver.position) > config.MAX_RANGE:
            logging.info("{a:06X}: receiver range check (even) failed".format(a=even_message.address))
            return

        odd_ecef = geodesy.llh2ecef((odd_lat,
                                     odd_lon,
                                     odd_message.altitude * constants.FTOM))
        if geodesy.ecef_distance(odd_ecef, receiver.position) > config.MAX_RANGE:
            logging.info("{a:06X}: receiver range check (odd) failed".format(a=odd_message.address))
            return

        if geodesy.ecef_distance(even_ecef, odd_ecef) > config.MAX_INTERMESSAGE_RANGE:
            logging.info("{a:06X}: intermessage range check failed".format(a=even_message.address))
            return

        # valid. Create a new sync point.
        if even_time < odd_time:
            syncpoint = SyncPoint(even_message.address, even_ecef, odd_ecef, interval)
        else:
            syncpoint = SyncPoint(even_message.address, odd_ecef, even_ecef, interval)

        syncpoint.receivers.append([receiver, tA, tB, False])
        if not syncpointlist:
            syncpointlist = self.sync_points[key] = []
        syncpointlist.append(syncpoint)

        # schedule cleanup of the syncpoint after 2 seconds -
        # we should have seen all copies of those messages by
        # then.
        asyncio.get_event_loop().call_later(
            2.0,
            functools.partial(self._cleanup_syncpoint,
                              key=key,
                              syncpoint=syncpoint))
Example #12
0
    def receiver_location_update(self, receiver, position_llh):
        """Note that a given receiver has moved."""
        receiver.position_llh = position_llh
        receiver.position = geodesy.llh2ecef(position_llh)

        self._compute_interstation_distances(receiver)