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