时间¶
时间管理在无线传感器网络中是一个重要的研究领域。它涉及到如何有效地协调和调度网络中的节点,以实现高效的数据采集、传输和处理。本节我们将介绍一些基础的时间管理概念,至于时间同步将会在其他章节中详细讨论。
在讨论具体概念之前,我们先来看一下相关的代码
time.hpp
#pragma once
#include <Arduino.h>
/*
* CalendarTime - Struct representing human-readable date and time.
*/
typedef struct
{
/* === Calendar Fields === */
uint16_t year; // Year (e.g., 2025)
uint8_t month; // Month [1-12]
uint8_t day; // Day [1-31]
uint8_t hour; // Hour [0-23]
uint8_t minute; // Minute [0-59]
uint8_t second; // Second [0-59]
int32_t ms; // Milliseconds [0-999]
} CalendarTime;
CalendarTime calendar_from_unix_seconds(uint64_t unix_seconds);
CalendarTime calendar_from_unix_milliseconds(uint64_t unix_ms);
uint64_t unix_from_calendar_seconds(const CalendarTime &cal);
uint64_t unix_from_calendar_milliseconds(const CalendarTime &cal);
CalendarTime YYMMDDHHMMSS2Calendar(const char *datetime12);
/*
* NodeTime - Unified time structure for embedded systems.
* Provides both UNIX timestamp and human-readable calendar format.
*/
class NodeTime
{
public:
/* === Running Time === */
uint64_t running_time; // Local running time in milliseconds since node startup
/* === Time Tracking === */
uint64_t last_sync_running_time; // Running time when last sync occurred
/* === Unified Time === */
float drift_ratio; // Clock drift ratio, applied as: adjusted = base * (1 + drift_ratio)
uint64_t time_offset; // Time offset in milliseconds for unified time correction
uint64_t unified_time; // Unified network time (in milliseconds)
CalendarTime calendar_time; // Human-readable calendar time
public:
/* === Constructors === */
NodeTime();
/* === Setters === */
void record_sync_time();
/* === Getters === */
uint64_t get_time(); // Get current unified time
CalendarTime get_calendar(); // Get calendar time (stub for now, no RTC parsing)
/* === Printout === */
void show_time();
};
extern NodeTime Time; // Global instance of NodeTime
time.cpp
#include "time.hpp"
/* === Helper Functions === */
CalendarTime calendar_from_unix_seconds(uint64_t unix_seconds)
{
CalendarTime cal;
uint64_t seconds = unix_seconds;
cal.second = seconds % 60;
seconds /= 60;
cal.minute = seconds % 60;
seconds /= 60;
cal.hour = seconds % 24;
seconds /= 24; // Total days since epoch
int year = 1970;
while (true)
{
bool is_leap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
int days_in_year = is_leap ? 366 : 365;
if (seconds >= days_in_year)
{
seconds -= days_in_year;
year++;
}
else
{
break;
}
}
cal.year = year;
static const uint8_t days_in_month[12] = {
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
int month = 0;
while (month < 12)
{
int dim = days_in_month[month];
if (month == 1 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)))
dim = 29;
if (seconds >= dim)
{
seconds -= dim;
month++;
}
else
{
break;
}
}
cal.month = month + 1;
cal.day = seconds + 1;
cal.ms = 0;
return cal;
}
CalendarTime calendar_from_unix_milliseconds(uint64_t unix_ms)
{
CalendarTime cal = calendar_from_unix_seconds(unix_ms / 1000);
cal.ms = unix_ms % 1000;
return cal;
}
uint64_t unix_from_calendar_seconds(const CalendarTime &cal)
{
uint64_t days = 0;
for (int y = 1970; y < cal.year; ++y)
{
bool is_leap = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0));
days += is_leap ? 366 : 365;
}
static const uint8_t days_in_month[12] = {
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
for (int m = 0; m < cal.month - 1; ++m)
{
if (m == 1 && (cal.year % 4 == 0 && (cal.year % 100 != 0 || cal.year % 400 == 0)))
days += 29;
else
days += days_in_month[m];
}
days += (cal.day - 1);
return days * 86400ULL + cal.hour * 3600 + cal.minute * 60 + cal.second;
}
uint64_t unix_from_calendar_milliseconds(const CalendarTime &cal)
{
return unix_from_calendar_seconds(cal) * 1000ULL + cal.ms;
}
// Convert YYMMDDHHMMSS string to CalendarTime structure
CalendarTime YYMMDDHHMMSS2Calendar(const char *datetime12)
{
CalendarTime ct = {0};
if (strlen(datetime12) != 12) {
// Return a zero-initialized CalendarTime on failure
return ct;
}
char buf[3] = {0};
// Extract and convert each part of the datetime string
strncpy(buf, datetime12, 2);
ct.year = 2000 + atoi(buf); // YY → 20YY
strncpy(buf, datetime12 + 2, 2);
ct.month = atoi(buf);
strncpy(buf, datetime12 + 4, 2);
ct.day = atoi(buf);
strncpy(buf, datetime12 + 6, 2);
ct.hour = atoi(buf);
strncpy(buf, datetime12 + 8, 2);
ct.minute = atoi(buf);
strncpy(buf, datetime12 + 10, 2);
ct.second = atoi(buf);
ct.ms = 0; // Milliseconds are set to 0 as per the original function
return ct;
}
/* === Major Functions === */
NodeTime::NodeTime()
{
running_time = 0;
last_sync_running_time = 0;
drift_ratio = 1.0f; // Default drift ratio (no drift)
time_offset = 0; // Default time offset
unified_time = 0;
calendar_time = {0, 0, 0, 0, 0, 0, 0}; // Initialize calendar time to zero
}
void NodeTime::record_sync_time()
{
last_sync_running_time = millis(); // Record the current running time
}
// uint64_t NodeTime::get_time()
// {
// running_time = millis(); // Get the current running time in milliseconds
// unified_time = static_cast<uint64_t>((drift_ratio * (running_time - last_sync_running_time)) + time_offset);
// return unified_time;
// }
// uint64_t NodeTime::get_time()
// {
// running_time = millis();
// // Use double to preserve precision during multiplication
// double delta = static_cast<double>(running_time - last_sync_running_time);
// return static_cast<uint64_t>(delta * drift_ratio + static_cast<double>(time_offset));
// }
// uint64_t NodeTime::get_time()
// {
// running_time = millis();
// // Use double to preserve precision during multiplication
// double delta = static_cast<double>(running_time - last_sync_running_time);
// // Include last_sync_running_time in the return value
// double adjusted_time = delta * drift_ratio + static_cast<double>(time_offset);
// // Return the result as uint64_t
// return static_cast<uint64_t>(adjusted_time);
// }
uint64_t NodeTime::get_time()
{
running_time = millis();
// Use double to preserve precision during multiplication
double delta = static_cast<double>(running_time - last_sync_running_time);
// Include last_sync_running_time in the return value
double adjusted_time = static_cast<double>(last_sync_running_time) + delta * drift_ratio + static_cast<double>(time_offset);
// Return the result as uint64_t
return static_cast<uint64_t>(adjusted_time);
}
CalendarTime NodeTime::get_calendar()
{
uint64_t current_time = get_time(); // Milliseconds since 1970-01-01 00:00:00 UTC
uint64_t ms_total = current_time;
calendar_time.ms = ms_total % 1000;
time_t seconds = ms_total / 1000;
// === Extract time ===
calendar_time.second = seconds % 60;
seconds /= 60;
calendar_time.minute = seconds % 60;
seconds /= 60;
calendar_time.hour = seconds % 24;
seconds /= 24; // Now we have total days since epoch
// === Extract date ===
int year = 1970;
while (true)
{
bool is_leap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
int days_in_year = is_leap ? 366 : 365;
if (seconds >= days_in_year)
{
seconds -= days_in_year;
year++;
}
else
{
break;
}
}
calendar_time.year = year;
static const uint8_t days_in_month[12] = {
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
int month = 0;
while (month < 12)
{
int dim = days_in_month[month];
// Adjust for leap year in February
if (month == 1 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)))
dim = 29;
if (seconds >= dim)
{
seconds -= dim;
month++;
}
else
{
break;
}
}
calendar_time.month = month + 1; // [1-12]
calendar_time.day = seconds + 1; // [1-31]
return calendar_time;
}
void NodeTime::show_time()
{
// Update current times
running_time = millis();
uint64_t now = get_time();
// Use unified function for calendar conversion
CalendarTime cal = calendar_from_unix_milliseconds(now);
Serial.println("=== Node Time Info ===");
Serial.print("Running Time : ");
Serial.print(running_time);
Serial.println(" ms");
Serial.print("Unified Time : ");
Serial.print(now);
Serial.println(" ms");
Serial.print("Calendar Time : ");
Serial.print(cal.year);
Serial.print("-");
if (cal.month < 10)
Serial.print("0");
Serial.print(cal.month);
Serial.print("-");
if (cal.day < 10)
Serial.print("0");
Serial.print(cal.day);
Serial.print(" ");
if (cal.hour < 10)
Serial.print("0");
Serial.print(cal.hour);
Serial.print(":");
if (cal.minute < 10)
Serial.print("0");
Serial.print(cal.minute);
Serial.print(":");
if (cal.second < 10)
Serial.print("0");
Serial.print(cal.second);
Serial.print(".");
if (cal.ms < 100)
Serial.print("0");
if (cal.ms < 10)
Serial.print("0");
Serial.println(cal.ms);
Serial.println("======================");
}
NodeTime Time;
时间的表示¶
自然记时¶
自然记时是指使用人类熟悉的日历和时钟格式来表示时间。它通常包括年、月、日、小时、分钟和秒等字段。这种方式易于理解和使用,但在计算机系统中处理起来可能不够高效。
Unix时间¶
Unix时间是指自1970年1月1日00:00:00 UTC以来的秒数。它是一种标准的时间表示方式,广泛用于计算机系统中。Unix时间的优点是易于计算和比较,但不易于人类阅读。网络对时通常使用Unix时间来同步系统时间。
运行时间¶
运行时间是指从系统启动到当前时刻的时间长度,通常以毫秒为单位。它对于实时系统和嵌入式设备非常重要,因为它可以帮助我们了解系统的运行状态和性能。通常硬件平台会提供一个计时器来跟踪运行时间。
注意
在实际应用中,通常会将自然记时和Unix时间进行转换,以便在需要人类可读格式时使用自然记时,而在需要高效计算时使用Unix时间。本项目中,我们不需要特别高的精度,但是为了保证功能的可靠性,我们需要使用毫秒级的时间表示。
总结
从时间的表示上我们可以看到,三中方式各有优缺点。自然记时易于人类理解,但计算机处理起来不够高效;Unix时间便于计算和比较,但不易于人类阅读;运行时间精度高,但是它是相对于系统启动时间的,对于无线传感器网络来说,通常需要与其他节点进行同步。为了满足这些需求,我们在time模块中定义了一个统一的时间结构NodeTime
,它包含了Unix时间、自然记时和运行时间的相关字段,也包含了一些辅助函数来进行时间的转换和计算。
为了方便计算,我们定义了三个个时间变量:
Time
:全局时间变量,用于表示当前系统时间。