Newer
Older
Tardis / src / Clock.cpp
/*
SPDX-License-Identifier: MIT
Copyright (c) 2024 John Watts and the LuminaSensum contributors
*/

#include "Clock.h"
#include "mbed.h"

using namespace std::chrono;

// The kernel's internal clock duration
// As of writing this is a millisecond
typedef Kernel::Clock::duration kernel_duration;

// The time the system started in microseconds since 1970
microseconds boot_start;
// Always use this lock to access boot_start
Mutex boot_start_access;

// Sets the boot_start time based on kernel uptime and real time
// in microseconds since 1970
// This is done by removing the uptime from the real time to get the
// time the system booted
// Both kernel_time and real_time should be sampled at the same time
void set_boot_start(kernel_timepoint kernel_time, microseconds real_time) {
	microseconds uptime = kernel_time.time_since_epoch();
	boot_start_access.lock();
	boot_start = real_time - uptime;
	boot_start_access.unlock();
}

// Converts a kernel timepoint to microseconds since 1970
// This is done by adding boot_start to the kernel time passed
microseconds kernel_to_real_time(kernel_timepoint kernel_time) {
	microseconds boot_time = kernel_time.time_since_epoch();
	boot_start_access.lock();
	microseconds real_time = boot_start + boot_time;
	boot_start_access.unlock();
	return real_time;
}

// Converts microseconds since 1970 to a kernel timepoint
// This is done by subtracting boot_start from the real time passed
// The microseconds will be rounded to the kernel clock duration
kernel_timepoint real_to_kernel_time(microseconds real_time) {
	boot_start_access.lock();
	microseconds uptime = real_time - boot_start;
	boot_start_access.unlock();
	kernel_duration uptime_rounded = round<kernel_duration>(uptime);
	kernel_timepoint kernel_time(uptime_rounded);
	return kernel_time;
}

// Initializes the clock, run once at boot
void clock_setup(void) {
	// Get RTC time
	time_t rtc_time = time(NULL);
	// Update boot start time
	microseconds rtc_time_microsecs = seconds(rtc_time);
	kernel_timepoint kernel_now = Kernel::Clock::now();
	set_boot_start(kernel_now, rtc_time_microsecs);
}

// Syncs the clock using SNTP, run any time
SNTPError clock_sync(NetworkInterface &net) {
	// Get time from SNTP
	struct timeval time;
	SNTPError err;
	err = sntp(net, "time.google.com", 123, &time);
	if (err != SNTPSuccess) {
		return err;
	}
	// Set RTC time
	set_time(time.tv_sec);
	// Convert SNTP timeval to microseconds
	microseconds time_microsecs(0);
	time_microsecs += seconds(time.tv_sec);
	time_microsecs += microseconds(time.tv_usec);
	// Update boot start time
	kernel_timepoint kernel_now = Kernel::Clock::now();
	set_boot_start(kernel_now, time_microsecs);
	return SNTPSuccess;
}

// Gets the current clock time in microseconds since 1970
microseconds clock_now(void) {
	kernel_timepoint kernel_now = Kernel::Clock::now();
	return kernel_to_real_time(kernel_now);
}

// Formats a time in microseconds using strftime
// Returned value is a buffer containing the formatted string
// or "(time overflow)"
// Calling this function overwrites the previous buffer contents
// so if you want to preserve it please copy the string
const char *clock_timestring(microseconds time, const char *format) {
	time_t time_seconds = ceil<seconds>(time).count();
	struct tm *local_time = localtime(&time_seconds);

	static char buffer[64];
	size_t buffer_size = sizeof(buffer);
	size_t buffer_written;
	buffer_written = strftime(buffer, buffer_size, format, local_time);

	if (buffer_written == 0) {
		// It overflowed, so return something useful
		return "(time overflow)";
	} else {
		return buffer;
	}
}

// Formats a time in microseconds according to ISO 8601
// Returned value is a buffer containing the formatted string
// or "(iso8601 overflow)"
// Calling this function overwrites the previous buffer contents
// so if you want to preserve it please copy the string
const char *clock_iso8601string(microseconds time) {
	const char *timestring = clock_timestring(time, "%Y-%m-%dT%H:%M:%S");
	long only_microseconds = time.count() % 1000000;

	static char buffer[128];
	int buffer_size = sizeof(buffer);
	int written;
	written = snprintf(
		buffer, buffer_size, "%s.%06li", timestring, only_microseconds);

	if (written >= buffer_size) {
		// It overflowed, so return something useful
		return "(iso8601 overflow)";
	} else {
		return buffer;
	}
}

// Formats the current time in microseconds according to ISO 8601
// This is a quick wrapper for clock_iso8601string using the current
// clock time. Read its documentation for full usage details
const char *clock_iso8601string_now(void) {
	return clock_iso8601string(clock_now());
}