diff --git a/include/hubble/sat/ephemeris.h b/include/hubble/sat/ephemeris.h index e002002..e32d120 100644 --- a/include/hubble/sat/ephemeris.h +++ b/include/hubble/sat/ephemeris.h @@ -93,8 +93,10 @@ struct hubble_sat_device_region { struct hubble_sat_pass_info { /** Longitude of the satellite pass (degrees, East positive). */ double lon; - /** Time of the satellite pass (Unix time, seconds since epoch). */ - uint64_t t; + /** Pass start time (Unix time, seconds since epoch). */ + uint64_t start; + /** Time the satellite reaches culmination (Unix time, seconds since epoch). */ + uint64_t culmination; /** Time duration of the pass in seconds. */ uint32_t duration; /** True if the satellite is ascending (moving northward), false if descending. */ diff --git a/src/hubble_priv.h b/src/hubble_priv.h index 5f21794..c4bbd43 100644 --- a/src/hubble_priv.h +++ b/src/hubble_priv.h @@ -119,4 +119,33 @@ int hubble_internal_sat_init(void); */ void hubble_internal_channel_hopping_sequence_set(void); + +/** + * @brief Get the estimated clock drift since the last time sync. + * + * Computes the accumulated drift based on the elapsed time since the + * last successful time synchronization and the configured device + * time drift rate (in PPM). This value is used to compensate for + * hardware clock inaccuracies by, for example, adding extra + * transmission retries proportional to the elapsed drift. + * + * @return Estimated drift in milliseconds. + */ +uint32_t hubble_internal_time_drift_get(void); + +/** + * @brief Get the total transmission period for a satellite packet. + * + * Returns the worst-case duration of a satellite packet transmission + * in the normal mode, including the base number of retries plus any + * additional retries added to compensate for the estimated clock + * drift since the last time synchronization. + * + * The returned value is computed as: + * (base_retries + drift_retries) * retransmission_interval + * + * @return Total transmission period in milliseconds. + */ +uint32_t hubble_internal_sat_transmission_period_get(void); + #endif /* SRC_HUBBLE_PRIV_H */ diff --git a/src/hubble_sat.c b/src/hubble_sat.c index 6a9d06d..a228298 100644 --- a/src/hubble_sat.c +++ b/src/hubble_sat.c @@ -54,31 +54,50 @@ static int _transmission_params_get(enum hubble_sat_transmission_mode mode, return ret; } +uint32_t hubble_internal_time_drift_get(void) +{ + uint64_t elapsed_ms; + uint64_t drift_ms; + + elapsed_ms = hubble_time_get() - hubble_internal_time_last_synced_get(); + + drift_ms = + (elapsed_ms * CONFIG_HUBBLE_SAT_NETWORK_DEVICE_TDR) / 1000000ULL; + + return (uint32_t)HUBBLE_MIN(UINT32_MAX, drift_ms); +} + static uint8_t _additional_retries_count(uint8_t interval_s) { uint8_t ret; - uint64_t synced_interval_s; + uint32_t drift_ms; if (interval_s == 0U) { return 0; } - synced_interval_s = - (hubble_time_get() - hubble_internal_time_last_synced_get()) / - 1000; + drift_ms = hubble_internal_time_drift_get(); - HUBBLE_LOG_DEBUG("Interval since time was synced: %lu seconds", - synced_interval_s); + HUBBLE_LOG_DEBUG("Time drift since last sync: %u ms", drift_ms); - ret = HUBBLE_MIN(UINT8_MAX, (synced_interval_s * - CONFIG_HUBBLE_SAT_NETWORK_DEVICE_TDR) / - (1000000ULL * interval_s)); + ret = HUBBLE_MIN(UINT8_MAX, drift_ms / (1000U * interval_s)); HUBBLE_LOG_DEBUG("Number of additional retries due TDR: %u", ret); return ret; } +uint32_t hubble_internal_sat_transmission_period_get(void) +{ + uint8_t additional_retries = + _additional_retries_count(_SAT_RETRANSMISSION_INTERVAL_NORMAL_S); + + /* x1000U to return the interval in ms */ + return ((_SAT_RETRANSMISSION_RETRIES_NORMAL + additional_retries) * + _SAT_RETRANSMISSION_INTERVAL_NORMAL_S) * + 1000U; +} + int hubble_internal_sat_init(void) { int ret; diff --git a/src/hubble_sat_ephemeris.c b/src/hubble_sat_ephemeris.c index 3038635..65bc9f5 100644 --- a/src/hubble_sat_ephemeris.c +++ b/src/hubble_sat_ephemeris.c @@ -8,6 +8,8 @@ #include #include +#include "hubble_priv.h" + #ifndef M_PI #define M_PI 3.14159265358979323846 #endif @@ -497,14 +499,14 @@ static int _next_pass_get(const struct hubble_sat_orbital_params *orbit, } /* Iterate until a valid pass is found */ - while (pass->t == 0 && + while (pass->culmination == 0 && (HUBBLE_TWO_PI_DEGREES - _zero_to_360(pos->lon - lon_tol - crossings[index].lon) < HUBBLE_PI_DEGREES)) { if ((fabs(_minus_180_to_180(crossings[index].lon - pos->lon)) <= lon_tol) && (crossings[index].t > t)) { - pass->t = crossings[index].t; + pass->culmination = crossings[index].t; pass->lon = crossings[index].lon; pass->ascending = (ascending) ? pos->lat > 0 : pos->lat <= 0; @@ -548,18 +550,18 @@ static int _pass_get(const struct hubble_sat_orbital_params *orbit, uint64_t t, if ((fabs(_minus_180_to_180(crossings[0].lon - pos->lon)) <= lon_tol) && (crossings[0].t > t)) { - pass->t = crossings[0].t; + pass->culmination = crossings[0].t; pass->lon = crossings[0].lon; pass->ascending = pos->lat > 0; } else if ((fabs(_minus_180_to_180(crossings[1].lon - pos->lon)) <= lon_tol) && (crossings[1].t > t)) { - pass->t = crossings[1].t; + pass->culmination = crossings[1].t; pass->lon = crossings[1].lon; pass->ascending = pos->lat <= 0; } - while (pass->t == 0) { + while (pass->culmination == 0) { int ret; double delta_lon_a = @@ -615,6 +617,7 @@ int hubble_next_pass_get(uint64_t t, const struct hubble_sat_device_pos *pos, { struct hubble_sat_pass_info next_pass; double lon_tol; + uint16_t transmission_period_s; /* Basic sanity check */ if ((pos == NULL) || (pass == NULL)) { @@ -632,7 +635,7 @@ int hubble_next_pass_get(uint64_t t, const struct hubble_sat_device_pos *pos, HUBBLE_LOG_DEBUG("Searching next pass from t=%llu lat=%f lon=%f", (unsigned long long)t, pos->lat, pos->lon); - pass->t = UINT64_MAX; + pass->culmination = UINT64_MAX; for (size_t i = 0; i < _satellites_count; i++) { double alt = _sat_altitude_get(&_satellites[i], t); @@ -640,21 +643,37 @@ int hubble_next_pass_get(uint64_t t, const struct hubble_sat_device_pos *pos, int ret = _pass_get(&_satellites[i], t, pos, lon_tol, &next_pass); if (ret == 0) { - if (pass->t > next_pass.t) { + if (pass->culmination > next_pass.culmination) { *pass = next_pass; } } } - if (pass->t == UINT64_MAX) { + if (pass->culmination == UINT64_MAX) { HUBBLE_LOG_WARNING("Hubble Satellite next pass get: no pass " "found across %zu satellites", _satellites_count); return -ENOENT; } - HUBBLE_LOG_DEBUG("Next pass found: t=%llu lon=%f ascending=%d", - (unsigned long long)pass->t, pass->lon, + /* Compensate for clock drift: the device may be ahead or behind by up + * to hubble_internal_time_drift_get() ms, so start half a drift-window + * early to keep the pass centered on the actual reception window. + * + * hubble_internal_time_drift_get() returns ms so we need make it + * seconds and divide by 2. + */ + pass->culmination -= hubble_internal_time_drift_get() / 2000U; + + transmission_period_s = + hubble_internal_sat_transmission_period_get() / 1000U; + pass->start = pass->culmination - (transmission_period_s / 2U); + pass->duration = transmission_period_s; + + HUBBLE_LOG_DEBUG("Next pass found: start=%llu culmination=%llu lon=%f " + "ascending=%d", + (unsigned long long)pass->start, + (unsigned long long)pass->culmination, pass->lon, (int)pass->ascending); return 0; @@ -670,6 +689,7 @@ int hubble_next_pass_region_get(uint64_t t, double lat_min, lat_max; struct hubble_sat_device_pos pos; struct hubble_sat_pass_info next_pass; + uint16_t transmission_period_s; /* Basic sanity check */ if ((region == NULL) || (pass == NULL)) { @@ -701,7 +721,7 @@ int hubble_next_pass_region_get(uint64_t t, pos.lat = lat_mid; pos.lon = region->lon_mid; - pass->t = UINT64_MAX; + pass->culmination = UINT64_MAX; for (size_t i = 0; i < _satellites_count; i++) { int ret; @@ -711,7 +731,8 @@ int hubble_next_pass_region_get(uint64_t t, continue; } - orbit_count = _orbit_count_get(&_satellites[i], next_pass.t); + orbit_count = + _orbit_count_get(&_satellites[i], next_pass.culmination); if (orbit_count < 0) { continue; } @@ -778,23 +799,33 @@ int hubble_next_pass_region_get(uint64_t t, } } - if (pass->t > next_pass.t) { + if (pass->culmination > next_pass.culmination) { *pass = next_pass; } } - if (pass->t == UINT64_MAX) { + if (pass->culmination == UINT64_MAX) { HUBBLE_LOG_WARNING("Hubble Satellite next pass region get: no " "pass found across %zu satellites", _satellites_count); return -ENOENT; } - pass->t -= pass->duration / 2; + transmission_period_s = + hubble_internal_sat_transmission_period_get() / 1000U; + + pass->duration += transmission_period_s; + + /* Compensate the drift the same way we do on hubble_next_pass_get() */ + pass->culmination -= hubble_internal_time_drift_get() / 2000U; + + pass->start = pass->culmination - (pass->duration / 2U); - HUBBLE_LOG_DEBUG("Next region pass found: t=%llu lon=%f duration=%llu " + HUBBLE_LOG_DEBUG("Next region pass found: start=%llu culmination=%llu " + "lon=%f duration=%llu " "ascending=%d", - (unsigned long long)pass->t, pass->lon, + (unsigned long long)pass->start, + (unsigned long long)pass->culmination, pass->lon, (unsigned long long)pass->duration, (int)pass->ascending); diff --git a/tests/zephyr/ephemeris/src/main.c b/tests/zephyr/ephemeris/src/main.c index f751cec..2d9ecfe 100644 --- a/tests/zephyr/ephemeris/src/main.c +++ b/tests/zephyr/ephemeris/src/main.c @@ -8,12 +8,17 @@ #include #include -#define EPHEMERIS_DELTA (3) +#define EPHEMERIS_DELTA (3) + +/* The transmission period is the product of the number of retries + + * the interval between them. + */ +#define TRANSMISSION_PERIOD_SINGLE_PACKET (160U) struct test_result { struct hubble_sat_device_pos pos; uint64_t start_time; - uint64_t next_pass_time; + uint64_t culmination_time; }; static const struct test_result results[] = { @@ -125,8 +130,8 @@ ZTEST(satellite_ephemeris_test, test_satellite_ephemeris_calculation) &(results[count].pos), &next_pass); zassert_equal(ret, 0, NULL); - zassert_within(next_pass.t, results[count].next_pass_time, - EPHEMERIS_DELTA); + zassert_within(next_pass.culmination, + results[count].culmination_time, EPHEMERIS_DELTA); } } @@ -172,17 +177,17 @@ ZTEST(satellite_ephemeris_test, test_satellite_ephemeris_invalid) struct test_region_result { struct hubble_sat_device_region region; uint64_t start_time; - uint64_t next_pass_time; + uint64_t culmination_time; uint32_t duration; }; static const struct test_region_result region_results[] = { - {{1.0, 30.0, -45.0, 50.0}, 1711296587, 1711299181, 477}, - {{1.0, 30.0, -45.0, 50.0}, 1711299660, 1711336226, 479}, - {{-45.0, 30.0, -45.0, 50.0}, 1711296587, 1711299912, 482}, - {{-45.0, 30.0, -45.0, 50.0}, 1711335912, 1711341182, 484}, - {{45.0, 30.0, -45.0, 50.0}, 1711296587, 1711298475, 483}, - {{45.0, 30.0, -45.0, 50.0}, 1711334475, 1711336929, 484}, + {{1.0, 30.0, -45.0, 50.0}, 1711296587, 1711299419, 477}, + {{1.0, 30.0, -45.0, 50.0}, 1711299660, 1711336468, 479}, + {{-45.0, 30.0, -45.0, 50.0}, 1711296587, 1711300154, 482}, + {{-45.0, 30.0, -45.0, 50.0}, 1711335912, 1711341426, 484}, + {{45.0, 30.0, -45.0, 50.0}, 1711296587, 1711298717, 483}, + {{45.0, 30.0, -45.0, 50.0}, 1711334475, 1711337171, 484}, }; ZTEST(satellite_ephemeris_test, test_satellite_ephemeris_region_calculation) @@ -199,10 +204,13 @@ ZTEST(satellite_ephemeris_test, test_satellite_ephemeris_region_calculation) &(region_results[count].region), &next_pass); zassert_equal(ret, 0, NULL); - zassert_within(next_pass.t, region_results[count].next_pass_time, + zassert_within(next_pass.culmination, + region_results[count].culmination_time, EPHEMERIS_DELTA); zassert_within(next_pass.duration, - region_results[count].duration, EPHEMERIS_DELTA); + region_results[count].duration + + TRANSMISSION_PERIOD_SINGLE_PACKET, + EPHEMERIS_DELTA); } }