SEDSnet 4.0.0

A memory safe, no_std-capable networking stack with routing, discovery, reliability, and Rust/C/Python bindings.
Documentation
#include "telemetry.h"
#include "sedsnet_c_wrapper.h"

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <stdarg.h>

static uint8_t g_local_unix_valid = 0U;
static uint64_t g_local_unix_ms = 0ULL;

static uint64_t host_now_ms(void *user) {
  (void)user;
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)(tv.tv_usec / 1000ULL);
}

RouterState g_router = {.r = NULL, .created = 0U, .start_time = 0ULL};

SedsResult tx_send(const uint8_t *bytes, size_t len, void *user) {
  (void)bytes;
  (void)len;
  (void)user;
  return SEDS_OK;
}

void rx_asynchronous(const uint8_t *bytes, size_t len) {
  if (!bytes || len == 0U) {
    return;
  }
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return;
  }
  (void)seds_router_rx_packed_packet_to_queue(g_router.r, bytes, len);
}

SedsResult on_radio_packet(const SedsPacketView *pkt, void *user) {
  (void)user;
  char buf[seds_pkt_to_string_len(pkt)];
  SedsResult s = seds_pkt_to_string(pkt, buf, sizeof(buf));
  if (s != SEDS_OK) {
    printf("on_radio_packet: seds_pkt_to_string failed: %d\n", (int)s);
    return s;
  }
  printf("on_radio_packet: %s\n", buf);
  return SEDS_OK;
}

static int is_leap_year(int32_t year) {
  return ((year % 4) == 0 && (year % 100) != 0) || ((year % 400) == 0);
}

static uint64_t days_before_year(int32_t year) {
  uint64_t days = 0;
  for (int32_t y = 1970; y < year; ++y) {
    days += is_leap_year(y) ? 366ULL : 365ULL;
  }
  return days;
}

static uint64_t unix_ms_from_utc(int32_t year, uint8_t month, uint8_t day,
                                 uint8_t hour, uint8_t minute, uint8_t second,
                                 uint16_t millisecond) {
  static const uint16_t days_before_month[12] = {0U, 31U, 59U, 90U, 120U, 151U,
                                                 181U, 212U, 243U, 273U, 304U, 334U};
  uint64_t days = days_before_year(year) + days_before_month[month - 1U] + (uint64_t)(day - 1U);
  if (month > 2U && is_leap_year(year)) {
    days += 1ULL;
  }
  return (((days * 24ULL + hour) * 60ULL + minute) * 60ULL + second) * 1000ULL + millisecond;
}

static SedsResult apply_local_unix_to_router(SedsRouter *router) {
  static const uint8_t days_in_month[12] = {31U, 28U, 31U, 30U, 31U, 30U,
                                            31U, 31U, 30U, 31U, 30U, 31U};
  uint64_t whole_seconds;
  uint64_t days;
  uint32_t seconds_of_day;
  int32_t year = 1970;
  uint8_t month = 1U;
  uint8_t day = 1U;
  uint8_t hour;
  uint8_t minute;
  uint8_t second;
  uint16_t millisecond;

  if (!router || !g_local_unix_valid) {
    return SEDS_OK;
  }

  whole_seconds = g_local_unix_ms / 1000ULL;
  days = whole_seconds / 86400ULL;
  seconds_of_day = (uint32_t)(whole_seconds % 86400ULL);
  millisecond = (uint16_t)(g_local_unix_ms % 1000ULL);

  while (1) {
    uint32_t days_in_year = is_leap_year(year) ? 366U : 365U;
    if (days < days_in_year) {
      break;
    }
    days -= days_in_year;
    ++year;
  }

  for (month = 1U; month <= 12U; ++month) {
    uint32_t dim = days_in_month[month - 1U];
    if (month == 2U && is_leap_year(year)) {
      dim = 29U;
    }
    if (days < dim) {
      day = (uint8_t)(days + 1U);
      break;
    }
    days -= dim;
  }

  hour = (uint8_t)(seconds_of_day / 3600U);
  minute = (uint8_t)((seconds_of_day % 3600U) / 60U);
  second = (uint8_t)(seconds_of_day % 60U);

  return seds_router_set_local_network_datetime_millis(router, year, month, day, hour, minute,
                                                       second, millisecond);
}

static SedsResult refresh_local_unix_from_router(void) {
  if (!g_router.r) {
    return SEDS_ERR;
  }
  if (seds_router_get_network_time_ms(g_router.r, &g_local_unix_ms) != SEDS_OK) {
    return SEDS_ERR;
  }
  g_local_unix_valid = 1U;
  return SEDS_OK;
}

SedsResult init_telemetry_router(void) {
  if (g_router.created && g_router.r) {
    return SEDS_OK;
  }

  SedsEndpointRef radio_endpoint;
  SedsResult endpoint_result = seds_endpoint_ref_by_name(SEDS_NAME_LITERAL("RADIO"), &radio_endpoint);
  if (endpoint_result != SEDS_OK) {
    printf("Error: RADIO endpoint is not registered\n");
    return endpoint_result;
  }

  const SedsLocalEndpointDesc locals[] = {
      {.endpoint = radio_endpoint.id, .packet_handler = on_radio_packet, .user = NULL},
  };

  SedsRouter *r = seds_router_new(Seds_RM_Sink, host_now_ms, NULL, locals,
                                  sizeof(locals) / sizeof(locals[0]));
  if (!r) {
    printf("Error: failed to create router\n");
    return SEDS_ERR;
  }

  if (seds_router_add_side_packed(r, "TX", 2, tx_send, NULL, true) < 0) {
    printf("Error: failed to add router side\n");
    seds_router_free(r);
    return SEDS_ERR;
  }

  if (seds_router_configure_timesync(r, true, 1U, 10U, 5000U, 1000U, 1000U) != SEDS_OK) {
    printf("Error: failed to configure time sync\n");
    seds_router_free(r);
    return SEDS_ERR;
  }

  if (g_local_unix_valid) {
    (void)apply_local_unix_to_router(r);
  }

  g_router.r = r;
  g_router.created = 1U;
  g_router.start_time = host_now_ms(NULL);
  return SEDS_OK;
}

SedsResult log_telemetry_synchronous(SedsTypeRef data_type, const void *data,
                                     size_t element_count, size_t element_size) {
  if (!data || element_count == 0U || element_size == 0U) {
    return SEDS_BAD_ARG;
  }
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  return seds_router_log(g_router.r, data_type.id, data, element_count * element_size);
}

SedsResult log_telemetry_asynchronous(SedsTypeRef data_type, const void *data,
                                      size_t element_count, size_t element_size) {
  if (!data || element_count == 0U || element_size == 0U) {
    return SEDS_BAD_ARG;
  }
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  return seds_router_log_queue(g_router.r, data_type.id, data, element_count * element_size);
}

SedsResult log_telemetry_string_asynchronous(SedsTypeRef data_type, const char *str) {
  if (!str) {
    return SEDS_BAD_ARG;
  }
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  return seds_router_log_string_ex(g_router.r, data_type.id, str, strlen(str), NULL, 1);
}

SedsResult dispatch_tx_queue(void) {
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  return seds_router_process_tx_queue(g_router.r);
}

SedsResult process_rx_queue(void) {
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  return seds_router_process_rx_queue(g_router.r);
}

SedsResult dispatch_tx_queue_timeout(uint32_t timeout_ms) {
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  return seds_router_process_tx_queue_with_timeout(g_router.r, timeout_ms);
}

SedsResult process_rx_queue_timeout(uint32_t timeout_ms) {
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  return seds_router_process_rx_queue_with_timeout(g_router.r, timeout_ms);
}

SedsResult process_all_queues_timeout(uint32_t timeout_ms) {
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  return seds_router_process_all_queues_with_timeout(g_router.r, timeout_ms);
}

SedsResult telemetry_periodic(uint32_t timeout_ms) {
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  SedsResult result = seds_router_periodic(g_router.r, timeout_ms);
  if (result == SEDS_OK) {
    (void)refresh_local_unix_from_router();
  }
  return result;
}

SedsResult telemetry_poll_timesync(void) {
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  SedsResult result = seds_router_poll_timesync(g_router.r, NULL);
  if (result == SEDS_OK) {
    (void)refresh_local_unix_from_router();
  }
  return result;
}

SedsResult telemetry_announce_discovery(void) {
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  return seds_router_announce_discovery(g_router.r);
}

SedsResult telemetry_poll_discovery(void) {
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }
  return seds_router_poll_discovery(g_router.r, NULL);
}

uint64_t telemetry_now_ms(void) { return host_now_ms(NULL); }

uint64_t telemetry_unix_ms(void) { return g_local_unix_valid ? g_local_unix_ms : 0ULL; }
uint64_t telemetry_unix_s(void) { return telemetry_unix_ms() / 1000ULL; }
uint8_t telemetry_unix_is_valid(void) { return g_local_unix_valid; }

void telemetry_set_unix_time_ms(uint64_t unix_ms) {
  g_local_unix_valid = unix_ms != 0ULL ? 1U : 0U;
  g_local_unix_ms = unix_ms;
  if (g_router.r) {
    (void)apply_local_unix_to_router(g_router.r);
  }
}

static SedsResult log_error_impl(uint8_t queue, const char *fmt, va_list args) {
  int written;
  char buf[512];

  if (!fmt) {
    return SEDS_BAD_ARG;
  }
  if (!g_router.r && init_telemetry_router() != SEDS_OK) {
    return SEDS_ERR;
  }

  written = vsnprintf(buf, sizeof(buf), fmt, args);
  if (written < 0) {
    return seds_router_log_string_ex(g_router.r, SEDS_DT_TELEMETRY_ERROR, "", 0U, NULL, queue);
  }

  return seds_router_log_string_ex(g_router.r, SEDS_DT_TELEMETRY_ERROR, buf,
                                   (size_t)((written < (int)sizeof(buf)) ? written : (int)sizeof(buf) - 1),
                                   NULL, queue);
}

SedsResult log_error_asynchronous(const char *fmt, ...) {
  va_list args;
  SedsResult result;
  va_start(args, fmt);
  result = log_error_impl(1U, fmt, args);
  va_end(args);
  return result;
}

SedsResult log_error_synchronous(const char *fmt, ...) {
  va_list args;
  SedsResult result;
  va_start(args, fmt);
  result = log_error_impl(0U, fmt, args);
  va_end(args);
  return result;
}

SedsResult log_error_asyncronous(const char *fmt, ...) {
  va_list args;
  SedsResult result;
  va_start(args, fmt);
  result = log_error_impl(1U, fmt, args);
  va_end(args);
  return result;
}

SedsResult log_error_syncronous(const char *fmt, ...) {
  va_list args;
  SedsResult result;
  va_start(args, fmt);
  result = log_error_impl(0U, fmt, args);
  va_end(args);
  return result;
}

SedsResult print_telemetry_error(const int32_t error_code) {
  const int32_t need = seds_error_to_string_len(error_code);
  if (need <= 0) {
    return (SedsResult)need;
  }

  char buf[(size_t)need];
  SedsResult res = seds_error_to_string(error_code, buf, sizeof(buf));
  if (res == SEDS_OK) {
    printf("Error: %s\n", buf);
  }
  return res;
}