Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions include/hubble/sat/ephemeris.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
29 changes: 29 additions & 0 deletions src/hubble_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
37 changes: 28 additions & 9 deletions src/hubble_sat.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
65 changes: 48 additions & 17 deletions src/hubble_sat_ephemeris.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <string.h>
#include <errno.h>

#include "hubble_priv.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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)) {
Expand All @@ -632,29 +635,45 @@ 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);
lon_tol = _lon_tolerance_get(pos->lat, alt);
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;
Expand All @@ -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)) {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Comment on lines +814 to +817
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I'm missing sometihng. This somehow doesn't feel right to me. For region, we already have the duration that sat passes over a region right? hubble_internal_sat_transmission_period_get will give you the number of retries. But this total retries is not right. Aren't we supposed to somehow do this:
total retries = duration / retries_period?

For single point prediction, it makes sense to call hubble_internal_sat_transmission_period_get, but for a region, we know that the retries period (even with no clock drift) is 160s, and the duration for a region could be longer than that.

Since we don't control the transmission period for region, I feel like the math for single point prediction doesn't apply here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know the number of retries but we need to accommodate the situation were the device is located in the boundary of the region, if the device is in the boundary we can't simply wake up in the culmination time. This applies the same semantics, splitting the period of a single point transmission between the beginning and end the of the region. Remember that we calculate the duration based on the culmination time of when the sat enters and exits the region.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right you mentioned about the culmination time and I keep forgetting that the time sat enter/exit is at culmination. This makes sense then.


/* 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);

Expand Down
34 changes: 21 additions & 13 deletions tests/zephyr/ephemeris/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@
#include <zephyr/types.h>
#include <zephyr/ztest.h>

#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[] = {
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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)
Expand All @@ -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);
}
}

Expand Down
Loading