view gnss-galileo-state.c @ 64:8e85919405df

gnss-galileo: add a valid bit to each ephemeris This is easier than trying to guess whether the ephemeris contains any meaningful values. Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Wed, 22 Jan 2020 10:10:26 -0500
parents c8c848632471
children 123cac19d9bd
line wrap: on
line source

/*
 * Copyright (c) 2020 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <math.h>

#include "gnss-galileo.h"

#include <jeffpc/types.h>
#include <jeffpc/error.h>

static inline uint32_t tow2gst(uint32_t wn, uint32_t tow)
{
	return wn * 7 * 24 * 3600 + tow;
}

static inline bool verify_svid(unsigned int sv, bool almanac)
{
	/* For almanac entries, we accept sv 0 - which means unused entry */
	if ((sv >= (almanac ? 0 : 1)) && (sv <= GALILEO_MAX_SV_ID))
		return true;

	/*
	 * Ideally we could just return false here, but it is safer to catch
	 * problems early with a panic then to wonder why everything ended
	 * up corrupted.
	 */
	panic("Got a %s SV id %u", sv ? "reserved" : "invalid", sv);
}

void galileo_state_init(struct galileo_state *state,
			const struct galileo_state_ops *ops)
{
	int i, j;

	memset(state, 0, sizeof(struct galileo_state));

	state->ops = ops;

	/* use IOD that looks wrong */
	for (i = 0; i < ARRAY_LEN(state->sv); i++) {
		state->sv[i].eph.sv = i;
		state->sv[i].eph.iod = ~0;
	}

	/* use IOD that looks wrong */
	for (i = 0; i < ARRAY_LEN(state->wip); i++) {
		state->wip[i].nav.eph.sv = ~0;
		state->wip[i].nav.eph.iod = ~0;

		for (j = 0; j < ARRAY_LEN(state->wip[0].nav.iod); j++)
			state->wip[i].nav.iod[j] = ~0;
	}
}

static void galileo_state_sync_eph(struct galileo_state *state,
				   const unsigned int sv,
				   const uint32_t gst)
{
	const bool prev_wn = state->wip[sv].nav.eph.t0.raw > state->time.tow;
	const uint16_t ephwn = state->time.wn - (prev_wn ? 1 : 0);
	struct galileo_ephemeris prev;

	if ((state->wip[sv].nav.iod[0] != state->wip[sv].nav.iod[1]) ||
	    (state->wip[sv].nav.iod[1] != state->wip[sv].nav.iod[2]) ||
	    (state->wip[sv].nav.iod[2] != state->wip[sv].nav.iod[3]))
		return;

	if (state->wip[sv].nav.iod[0] == 0xffff)
		return;

	/*
	 * Compare the timestamps we stashed when receiving the ephemeris
	 * data with the just received timestamp (@gst) to decide if we may
	 * have received some of the ephemeris pages more than a week ago.
	 *
	 * This is not a 100% accurate check.  While it will *never* tell us
	 * incorrectly that the ephemeris is < 1 week old, it may tell us
	 * that the ephemeris is >= 1 week old when it isn't.  (Because we
	 * happened not to receive any GST messages but received the
	 * ephemeris data just fine.)  This is acceptable.
	 *
	 * Once we know that the ephemeris is < 1 week old, we can try to
	 * assign it a week number.  If more than a week passed, we have no
	 * idea which week the t0e belongs to.
	 *
	 * Put another way, we are looking at the last GST message from
	 * before the ephemeris messages and the current GST message.
	 * Visually:
	 *
	 *     <GST><ephemeris data><GST>
	 *
	 * We compare these "bookends" to figure out if more than a week has
	 * passed.
	 *
	 * Therefore,
	 *
	 * (1) if it has been less than a week, we assign a week number to
	 *     the emphemeris and start using it, and
	 * (2) if it has been more than a week, we simply invalidate the
	 *     ephemeris data.
	 *
	 * This still has its problems.  If for example we receive:
	 *
	 *	<GST><ephemeris data>[long delay]<GST>
	 *
	 * We will delay using the new ephemeris data for the duration of
	 * the delay.  This is however highly unlikely, since we check the
	 * ephemeris data for freshness whenever we receive a GST message
	 * from *any* sv.  Therefore, the "long delay" would have to involve
	 * not receiving *any* GST message from *all* sv.
	 */
	if (((gst - state->wip[sv].nav.gst[0]) >= (7 * 24 * 3600)) ||
	    ((gst - state->wip[sv].nav.gst[1]) >= (7 * 24 * 3600)) ||
	    ((gst - state->wip[sv].nav.gst[2]) >= (7 * 24 * 3600)) ||
	    ((gst - state->wip[sv].nav.gst[3]) >= (7 * 24 * 3600)))
		return; /* more than a week for at least one page */

	prev = state->sv[sv].eph;
	state->sv[sv].eph = state->wip[sv].nav.eph;
	state->sv[sv].eph.sv = sv;
	state->sv[sv].eph.iod = state->wip[sv].nav.iod[0];
	state->sv[sv].eph.valid = true;
	state->sv[sv].eph.t0.gst = tow2gst(ephwn, state->sv[sv].eph.t0.raw);
	state->sv[sv].valid_eph = true;

	if (state->ops->eph_update)
		state->ops->eph_update(state,
				       (prev.iod == 0xffff) ? NULL : &prev,
				       &state->sv[sv].eph);
}

static void __sync_time(struct galileo_state_time *time,
			uint32_t gst, uint16_t wn, uint32_t tow,
			bool allow_equal)
{
	/* sanity check */
	if (time->valid) {
		if (allow_equal) {
			ASSERT3U(time->gst, <=, gst);
			ASSERT3U(time->wn, <=, wn);
			if (time->wn == wn)
				ASSERT3U(time->tow, <=, tow);
		} else {
			ASSERT3U(time->gst, <, gst);
			ASSERT3U(time->wn, <=, wn);
			if (time->wn == wn)
				ASSERT3U(time->tow, <, tow);
		}
	}

	/* set */
	time->gst = gst;
	time->wn = wn;
	time->tow = tow;
	time->valid = true;
}

static void galileo_state_sync_time(struct galileo_state *state,
				    unsigned int sv, uint16_t wn,
				    uint32_t tow)
{
	const struct galileo_state_time prev_sv = state->sv[sv].last_time;
	const struct galileo_state_time prev_global = state->time;
	const uint32_t gst = tow2gst(wn, tow);
	int i;

	__sync_time(&state->sv[sv].last_time, gst, wn, tow, false);

	/*
	 * Use <= for the global time instead of < because another sv may
	 * have already updated the global system time values.
	 */
	__sync_time(&state->time, gst, wn, tow, true);

	/*
	 * If this is *not* the first time message from the constellation,
	 * we were able to tag all ephemeris pages received since then with
	 * that GST timestamp.  This timestamp is required to sync the
	 * ephemeris.
	 */
	if (prev_global.valid)
		for (i = 0; i < ARRAY_LEN(state->sv); i++)
			galileo_state_sync_eph(state, i, gst);

	if (state->ops->time)
		state->ops->time(state, sv, &state->time, &prev_sv);
}

bool galileo_state_apply_page(struct galileo_state *state,
			      const unsigned int sv,
			      const struct galileo_inav_page *pg)
{
	if (!verify_svid(sv, false))
		return false;

	if (!pg->page_nominal)
		return true; /* nothing to do for alerts */

	if (pg->nominal.type == 63)
		return true; /* nothing to do for dummy pages */

	if (pg->nominal.type == 1) {
		/* this SV's ephemeris */
		struct galileo_ephemeris *eph = &state->wip[sv].nav.eph;

		state->wip[sv].nav.gst[0] = state->time.gst;
		state->wip[sv].nav.iod[0] = pg->nominal.w1.iod;
		eph->t0.raw = pg->nominal.w1.t0e * 60;
		eph->m0 = ldexp(pg->nominal.w1.m0 * M_PI, -31);
		eph->e = ldexp(pg->nominal.w1.e, -33);
		eph->sqrt_a = ldexp(pg->nominal.w1.sqrt_a, -19);
	} else if (pg->nominal.type == 2) {
		/* this SV's ephemeris */
		struct galileo_ephemeris *eph = &state->wip[sv].nav.eph;

		state->wip[sv].nav.gst[1] = state->time.gst;
		state->wip[sv].nav.iod[1] = pg->nominal.w2.iod;
		eph->omega0 = ldexp(pg->nominal.w2.omega0 * M_PI, -31);
		eph->i0 = ldexp(pg->nominal.w2.i0 * M_PI, -31);
		eph->omega = ldexp(pg->nominal.w2.omega * M_PI, -31);
		eph->idot = ldexp(pg->nominal.w2.idot * M_PI, -43);
	} else if (pg->nominal.type == 3) {
		/* this SV's ephemeris */
		struct galileo_ephemeris *eph = &state->wip[sv].nav.eph;

		state->wip[sv].nav.gst[2] = state->time.gst;
		state->wip[sv].nav.iod[2] = pg->nominal.w3.iod;
		eph->omegadot = ldexp(pg->nominal.w3.omegadot * M_PI, -43);
		eph->delta_n = ldexp(pg->nominal.w3.delta_n * M_PI, -43);
		eph->cuc = ldexp(pg->nominal.w3.cuc, -29);
		eph->cus = ldexp(pg->nominal.w3.cus, -29);
		eph->crc = ldexp(pg->nominal.w3.crc, -5);
		eph->crs = ldexp(pg->nominal.w3.crs, -5);
	} else if (pg->nominal.type == 4) {
		/* this SV's ephemeris */
		struct galileo_ephemeris *eph = &state->wip[sv].nav.eph;

		ASSERT3U(sv, ==, pg->nominal.w4.svid);

		state->wip[sv].nav.gst[3] = state->time.gst;
		state->wip[sv].nav.iod[3] = pg->nominal.w4.iod;
		eph->cic = ldexp(pg->nominal.w4.cic, -29);
		eph->cis = ldexp(pg->nominal.w4.cis, -29);
	} else if (pg->nominal.type == 5) {
		/* GST */
		galileo_state_sync_time(state, sv,
					pg->nominal.w5.wn,
					pg->nominal.w5.tow);
	} else {
		return false; /* we don't know how to interpret this page */
	}

	return true;
}