跳转至

时间

时间管理在无线传感器网络中是一个重要的研究领域。它涉及到如何有效地协调和调度网络中的节点,以实现高效的数据采集、传输和处理。本节我们将介绍一些基础的时间管理概念,至于时间同步将会在其他章节中详细讨论。

在讨论具体概念之前,我们先来看一下相关的代码

time.hpp

#pragma once
#include <Arduino.h>

/*
 * MCUTime - Unified time structure for embedded systems.
 * Provides both UNIX timestamp and human-readable calendar format.
 */
class MCUTime
{
public:
    /* === UNIX Time Fields === */
    uint64_t unix_epoch; // Seconds since 1970-01-01 00:00:00 UTC
    uint64_t unix_ms;    // Milliseconds since 1970-01-01 00:00:00 UTC
    uint64_t estimated_unix_ms; // Estimated milliseconds since 1970-01-01 00:00:00 UTC
    uint64_t estimated_unix_epoch; // Estimated seconds since 1970-01-01 00:00:00 UTC

    /* === 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]

    /* === Time Tracking === */
    uint64_t last_update_ms; // Last update time in milliseconds by syncing
    uint64_t last_update_epoch; // Last update time in seconds since epoch by syncing
    uint64_t mcu_time_ms; // Current time in milliseconds since MCU start
    uint64_t mcu_base_ms; // Base time in milliseconds when synced
    uint64_t delta_ms; // Time since last update in milliseconds

public:
    /* === Constructors === */
    MCUTime(); // Default constructor initializes to 0

    /* === Setters === */
    void set_calendar();  // Set calendar fields based on current Unix time
    void set_time_ms(uint64_t ms); // Set Unix time by passing milliseconds since Unix epoch
    void set_time_epoch(uint64_t epoch); // Set Unix time by passing seconds since Unix epoch

    /* === Time Update === */
    uint64_t estimate_time_ms(); // estimate time ms based on MCU millis and last update
    uint64_t estimate_time_epoch(); // estimate time epoch based on MCU millis and last update

    /* === Getters === */
    uint64_t get_unix_ms() const;
    uint64_t get_unix_epoch() const;
    uint64_t get_now_time_ms() const; // Get current time in ms in Unix ms format
    uint64_t compute_ms_from_calendar() const; // Compute Unix ms from calendar fields
    String to_string() const; // Return "YYYY-MM-DD HH:MM:SS.mmm"
    void print() const;       // Print formatted string to Serial

    /* === Comparison === */
    int8_t compare_to(const MCUTime &other) const;
};

// Add at the bottom of the header
extern MCUTime Time;  // Global time variable
extern MCUTime SensingSchedule; // Time synchronization variable

time.cpp

#include "time.hpp"

/* Definition of the operator functions in the MCUTime class */
MCUTime::MCUTime()
{
    unix_epoch = 0;
    unix_ms = 0;
    estimated_unix_ms = 0;
    estimated_unix_epoch = 0;
    year = 1970;
    month = 1;
    day = 1;
    hour = 0;
    minute = 0;
    second = 0;
    ms = 0;
    last_update_ms = 0;
    last_update_epoch = 0;
    mcu_time_ms = 0;
    delta_ms = 0;
}

// Helper: check leap year
static bool is_leap_year(uint16_t year)
{
    return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}

// Helper: days in each month (non-leap year)
static const uint8_t days_in_month[] = {
    31, 28, 31, 30, 31, 30,
    31, 31, 30, 31, 30, 31};

void MCUTime::set_calendar()
{
    // Extract milliseconds
    ms = unix_ms % 1000;

    // Get total seconds
    uint64_t seconds = unix_ms / 1000;

    // Keep a backup in the object
    unix_epoch = seconds;

    // Calculate time-of-day
    second = seconds % 60;
    seconds /= 60;
    minute = seconds % 60;
    seconds /= 60;
    hour = seconds % 24;
    seconds /= 24;

    // Now calculate date from days since 1970-01-01
    uint32_t days = (uint32_t)seconds;
    year = 1970;

    while (true)
    {
        uint16_t days_in_year = is_leap_year(year) ? 366 : 365;
        if (days >= days_in_year)
        {
            days -= days_in_year;
            year++;
        }
        else
        {
            break;
        }
    }

    // Now determine the month and day
    uint8_t month_index = 0;
    while (true)
    {
        uint8_t dim = days_in_month[month_index];
        if (month_index == 1 && is_leap_year(year))
            dim++; // February in leap year
        if (days >= dim)
        {
            days -= dim;
            month_index++;
        }
        else
        {
            break;
        }
    }

    month = month_index + 1;
    day = days + 1;
}

void MCUTime::set_time_ms(uint64_t ms)
{
    unix_ms = ms;
    unix_epoch = ms / 1000;

    // Update calendar fields
    set_calendar();
}

// be very careful to use this function
void MCUTime::set_time_epoch(uint64_t epoch)
{
    unix_epoch = epoch;
    unix_ms = epoch * 1000 + (millis() % 1000); // Convert to ms, keeping current millis for ms part

    // Update calendar fields
    set_calendar();
}

uint64_t MCUTime::estimate_time_ms()
{
    // Calculate delta since last update
    mcu_time_ms = millis();
    delta_ms = mcu_time_ms - mcu_base_ms;

    estimated_unix_ms = last_update_ms + delta_ms;
    estimated_unix_epoch = last_update_epoch + (delta_ms / 1000);

    return estimated_unix_ms;
}

uint64_t MCUTime::estimate_time_epoch()
{
    // Calculate delta since last update
    mcu_time_ms = millis();
    delta_ms = mcu_time_ms - mcu_base_ms;

    estimated_unix_ms = last_update_ms + delta_ms;
    estimated_unix_epoch = last_update_epoch + (delta_ms / 1000);

    return estimated_unix_epoch;
}

uint64_t MCUTime::get_unix_ms() const
{
    return unix_ms;
}

uint64_t MCUTime::get_unix_epoch() const
{
    return unix_epoch;
}

uint64_t MCUTime::get_now_time_ms() const
{
    return millis();
}

uint64_t MCUTime::compute_ms_from_calendar() const
{
    uint64_t days = 0;

    for (uint16_t y = 1970; y < year; y++)
    {
        days += is_leap_year(y) ? 366 : 365;
    }

    for (uint8_t m = 1; m < month; m++)
    {
        days += days_in_month[m - 1];
        if (m == 2 && is_leap_year(year))
            days++;
    }

    days += (day - 1);

    uint64_t ret_ms = (days * 86400ULL + hour * 3600 + minute * 60 + second) * 1000;
    ret_ms += ms;  // Include milliseconds

    return ret_ms;
}


String MCUTime::to_string() const
{
    char buffer[30];
    snprintf(buffer, sizeof(buffer), "%04u-%02u-%02u %02u:%02u:%02u.%03d",
             year, month, day, hour, minute, second, ms);
    return String(buffer);
}

void MCUTime::print() const
{
    Serial.println(to_string());
}

int8_t MCUTime::compare_to(const MCUTime &other) const
{
    if (unix_ms < other.unix_ms)
        return -1; // this is earlier
    else if (unix_ms > other.unix_ms)
        return 1; // this is later
    else
        return 0; // they are equal
}

/* Global time variable */
MCUTime Time;

/* Sensing Config */
MCUTime SensingSchedule;

时间的表示

自然记时

自然记时是指使用人类熟悉的日历和时钟格式来表示时间。它通常包括年、月、日、小时、分钟和秒等字段。这种方式易于理解和使用,但在计算机系统中处理起来可能不够高效。

Unix时间

Unix时间是指自1970年1月1日00:00:00 UTC以来的秒数。它是一种标准的时间表示方式,广泛用于计算机系统中。Unix时间的优点是易于计算和比较,但不易于人类阅读。网络对时通常使用Unix时间来同步系统时间。

运行时间

运行时间是指从系统启动到当前时刻的时间长度,通常以毫秒为单位。它对于实时系统和嵌入式设备非常重要,因为它可以帮助我们了解系统的运行状态和性能。通常硬件平台会提供一个计时器来跟踪运行时间。

注意

在实际应用中,通常会将自然记时和Unix时间进行转换,以便在需要人类可读格式时使用自然记时,而在需要高效计算时使用Unix时间。本项目中,我们不需要特别高的精度,但是为了保证功能的可靠性,我们需要使用毫秒级的时间表示。

总结

从时间的表示上我们可以看到,三中方式各有优缺点。自然记时易于人类理解,但计算机处理起来不够高效;Unix时间便于计算和比较,但不易于人类阅读;运行时间精度高,但是它是相对于系统启动时间的,对于无线传感器网络来说,通常需要与其他节点进行同步。为了满足这些需求,我们在time模块中定义了一个统一的时间结构MCUTime,它包含了Unix时间、自然记时和运行时间的相关字段,也包含了一些辅助函数来进行时间的转换和计算。

为了方便计算,我们定义了两个时间变量:

  • Time:全局时间变量,用于表示当前系统时间。

  • SensingSchedule:设定采样时间变量,用于记录设定的采样时间。