1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-17 05:15:25 +03:00
betaflight/src/main/common/time.c
Bruce Luckcuck b8085b170e MSC on-board flash: Persist RTC across reboot and use as file timestamp
Adds support to persist the RTC (if set) across the reboot if entering mass storage mode for on-board flash. The value is then used as the timestamp for the files exposed in the virtual FAT32 filesystem. The files will then have reasonable creation dates when copied to the host computer.

If the RTC is not set (or supported), then the default timestamp of 2018-01-01 will be used (unchanged from previous).

Included some improvements to the RTC functions and exposed the `tz_offsetMinutes` in the `timeConfig` PG. Support already existed for timezone offsets but the parameter wasn't exposed to the user and couldn't be set.

Move timezone offset up a layer as a parameter to systemResetToMsc()
Adds support for specifying a custom timezone offset from both the CLI and MSP calls to enter mass storage mode.

Added an option timezone offset minutes to the CLI `msc` command. If no parameter is specified then the default as specified by `timezone_offset_minutes` will be used. So to reboot into mass storage mode and force the file timestamps to be in UTC, use `msc 0`.

Added reboot message `MSP_REBOOT_MSC_UTC` to support rebooting into mass storage mode and forcing the timestamps to use UTC time (0 offset). The Configurator will need to be modified to use this message for operating systems that expect UTC times for FAT file systems (like Linux).
2018-12-17 19:40:51 -05:00

294 lines
8.3 KiB
C

/*
* This file is part of INAV.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Alternatively, the contents of this file may be used under the terms
* of the GNU General Public License Version 3, as described below:
*
* This file is free software: you may copy, redistribute and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* @author Alberto Garcia Hierro <alberto@garciahierro.com>
*/
#include <stdint.h>
#include "common/maths.h"
#include "common/printf.h"
#include "common/time.h"
#include "drivers/persistent.h"
#include "pg/pg_ids.h"
#include "drivers/time.h"
#ifdef USE_RTC_TIME
// For the "modulo 4" arithmetic to work, we need a leap base year
#define REFERENCE_YEAR 2000
// Offset (seconds) from the UNIX epoch (1970-01-01) to 2000-01-01
#define EPOCH_2000_OFFSET 946684800
#define MILLIS_PER_SECOND 1000
// rtcTime_t when the system was started.
// Calculated in rtcSet().
static rtcTime_t started = 0;
static const uint16_t days[4][12] =
{
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
{ 366, 397, 425, 456, 486, 517, 547, 578, 609, 639, 670, 700},
{ 731, 762, 790, 821, 851, 882, 912, 943, 974, 1004, 1035, 1065},
{1096, 1127, 1155, 1186, 1216, 1247, 1277, 1308, 1339, 1369, 1400, 1430},
};
PG_REGISTER_WITH_RESET_TEMPLATE(timeConfig_t, timeConfig, PG_TIME_CONFIG, 0);
PG_RESET_TEMPLATE(timeConfig_t, timeConfig,
.tz_offsetMinutes = 0,
);
static rtcTime_t dateTimeToRtcTime(dateTime_t *dt)
{
unsigned int second = dt->seconds; // 0-59
unsigned int minute = dt->minutes; // 0-59
unsigned int hour = dt->hours; // 0-23
unsigned int day = dt->day - 1; // 0-30
unsigned int month = dt->month - 1; // 0-11
unsigned int year = dt->year - REFERENCE_YEAR; // 0-99
int32_t unixTime = (((year / 4 * (365 * 4 + 1) + days[year % 4][month] + day) * 24 + hour) * 60 + minute) * 60 + second + EPOCH_2000_OFFSET;
return rtcTimeMake(unixTime, dt->millis);
}
static void rtcTimeToDateTime(dateTime_t *dt, rtcTime_t t)
{
int32_t unixTime = t / MILLIS_PER_SECOND - EPOCH_2000_OFFSET;
dt->seconds = unixTime % 60;
unixTime /= 60;
dt->minutes = unixTime % 60;
unixTime /= 60;
dt->hours = unixTime % 24;
unixTime /= 24;
unsigned int years = unixTime / (365 * 4 + 1) * 4;
unixTime %= 365 * 4 + 1;
unsigned int year;
for (year = 3; year > 0; year--) {
if (unixTime >= days[year][0]) {
break;
}
}
unsigned int month;
for (month = 11; month > 0; month--) {
if (unixTime >= days[year][month]) {
break;
}
}
dt->year = years + year + REFERENCE_YEAR;
dt->month = month + 1;
dt->day = unixTime - days[year][month] + 1;
dt->millis = t % MILLIS_PER_SECOND;
}
static void rtcGetDefaultDateTime(dateTime_t *dateTime)
{
dateTime->year = 0;
dateTime->month = 1;
dateTime->day = 1;
dateTime->hours = 0;
dateTime->minutes = 0;
dateTime->seconds = 0;
dateTime->millis = 0;
}
static bool rtcIsDateTimeValid(dateTime_t *dateTime)
{
return (dateTime->year >= REFERENCE_YEAR) &&
(dateTime->month >= 1 && dateTime->month <= 12) &&
(dateTime->day >= 1 && dateTime->day <= 31) &&
(dateTime->hours <= 23) &&
(dateTime->minutes <= 59) &&
(dateTime->seconds <= 59) &&
(dateTime->millis <= 999);
}
static void dateTimeWithOffset(dateTime_t *dateTimeOffset, dateTime_t *dateTimeInitial, int16_t minutes)
{
rtcTime_t initialTime = dateTimeToRtcTime(dateTimeInitial);
rtcTime_t offsetTime = rtcTimeMake(rtcTimeGetSeconds(&initialTime) + minutes * 60, rtcTimeGetMillis(&initialTime));
rtcTimeToDateTime(dateTimeOffset, offsetTime);
}
static bool dateTimeFormat(char *buf, dateTime_t *dateTime, int16_t offsetMinutes, bool shortVersion)
{
dateTime_t local;
int tz_hours = 0;
int tz_minutes = 0;
bool retVal = true;
// Apply offset if necessary
if (offsetMinutes != 0) {
tz_hours = offsetMinutes / 60;
tz_minutes = ABS(offsetMinutes % 60);
dateTimeWithOffset(&local, dateTime, offsetMinutes);
dateTime = &local;
}
if (!rtcIsDateTimeValid(dateTime)) {
rtcGetDefaultDateTime(&local);
dateTime = &local;
retVal = false;
}
if (shortVersion) {
tfp_sprintf(buf, "%04u-%02u-%02u %02u:%02u:%02u",
dateTime->year, dateTime->month, dateTime->day,
dateTime->hours, dateTime->minutes, dateTime->seconds);
} else {
// Changes to this format might require updates in
// dateTimeSplitFormatted()
// Datetime is in ISO_8601 format, https://en.wikipedia.org/wiki/ISO_8601
tfp_sprintf(buf, "%04u-%02u-%02uT%02u:%02u:%02u.%03u%c%02d:%02d",
dateTime->year, dateTime->month, dateTime->day,
dateTime->hours, dateTime->minutes, dateTime->seconds, dateTime->millis,
tz_hours >= 0 ? '+' : '-', ABS(tz_hours), tz_minutes);
}
return retVal;
}
rtcTime_t rtcTimeMake(int32_t secs, uint16_t millis)
{
return ((rtcTime_t)secs) * MILLIS_PER_SECOND + millis;
}
int32_t rtcTimeGetSeconds(rtcTime_t *t)
{
return *t / MILLIS_PER_SECOND;
}
uint16_t rtcTimeGetMillis(rtcTime_t *t)
{
return *t % MILLIS_PER_SECOND;
}
bool dateTimeFormatUTC(char *buf, dateTime_t *dt)
{
return dateTimeFormat(buf, dt, 0, false);
}
bool dateTimeFormatLocal(char *buf, dateTime_t *dt)
{
return dateTimeFormat(buf, dt, timeConfig()->tz_offsetMinutes, false);
}
bool dateTimeFormatLocalShort(char *buf, dateTime_t *dt)
{
return dateTimeFormat(buf, dt, timeConfig()->tz_offsetMinutes, true);
}
void dateTimeUTCToLocal(dateTime_t *utcDateTime, dateTime_t *localDateTime)
{
dateTimeWithOffset(localDateTime, utcDateTime, timeConfig()->tz_offsetMinutes);
}
bool dateTimeSplitFormatted(char *formatted, char **date, char **time)
{
// Just look for the T and replace it with a zero
// XXX: Keep in sync with dateTimeFormat()
for (char *p = formatted; *p; p++) {
if (*p == 'T') {
*date = formatted;
*time = (p+1);
*p = '\0';
return true;
}
}
return false;
}
bool rtcHasTime(void)
{
return started != 0;
}
bool rtcGet(rtcTime_t *t)
{
if (!rtcHasTime()) {
return false;
}
*t = started + millis();
return true;
}
bool rtcSet(rtcTime_t *t)
{
started = *t - millis();
return true;
}
bool rtcGetDateTime(dateTime_t *dt)
{
rtcTime_t t;
if (rtcGet(&t)) {
rtcTimeToDateTime(dt, t);
return true;
}
// No time stored, fill dt with 0000-01-01T00:00:00.000
rtcGetDefaultDateTime(dt);
return false;
}
bool rtcSetDateTime(dateTime_t *dt)
{
rtcTime_t t = dateTimeToRtcTime(dt);
return rtcSet(&t);
}
void rtcPersistWrite(int16_t offsetMinutes)
{
rtcTime_t workTime;
uint32_t highLongWord = 0;
uint32_t lowLongWord = 0;
if (rtcGet(&workTime)) {
workTime += (offsetMinutes * 60 * MILLIS_PER_SECOND);
highLongWord = (uint32_t)(workTime >> 32);
lowLongWord = (uint32_t)(workTime & 0xffffffff);
}
persistentObjectWrite(PERSISTENT_OBJECT_RTC_HIGH, highLongWord);
persistentObjectWrite(PERSISTENT_OBJECT_RTC_LOW, lowLongWord);
}
bool rtcPersistRead(rtcTime_t *t)
{
const uint32_t highLongWord = persistentObjectRead(PERSISTENT_OBJECT_RTC_HIGH);
const uint32_t lowLongWord = persistentObjectRead(PERSISTENT_OBJECT_RTC_LOW);
if ((highLongWord != 0) || (lowLongWord != 0)) {
*t = ((uint64_t)highLongWord << 32) + lowLongWord;
return true;
} else {
return false;
}
}
#endif