diff --git a/make/source.mk b/make/source.mk index f5cd0f81b..fa96678cf 100644 --- a/make/source.mk +++ b/make/source.mk @@ -13,6 +13,7 @@ COMMON_SRC = \ common/maths.c \ common/printf.c \ common/streambuf.c \ + common/time.c \ common/typeconversion.c \ config/config_eeprom.c \ config/feature.c \ diff --git a/src/main/blackbox/blackbox.c b/src/main/blackbox/blackbox.c index 64cfb5b27..a915e979c 100644 --- a/src/main/blackbox/blackbox.c +++ b/src/main/blackbox/blackbox.c @@ -35,6 +35,7 @@ #include "common/axis.h" #include "common/encoding.h" #include "common/maths.h" +#include "common/time.h" #include "common/utils.h" #include "config/feature.h" @@ -1147,6 +1148,22 @@ static bool sendFieldDefinition(char mainFrameChar, char deltaFrameChar, const v return xmitState.headerIndex < headerCount; } +// Buf must be at least FORMATTED_DATE_TIME_BUFSIZE +STATIC_UNIT_TESTED char *blackboxGetStartDateTime(char *buf) +{ + #ifdef USE_RTC_TIME + dateTime_t dt; + // rtcGetDateTime will fill dt with 0000-01-01T00:00:00 + // when time is not known. + rtcGetDateTime(&dt); + dateTimeFormatLocal(buf, &dt); + #else + buf = "0000-01-01T00:00:00.000"; + #endif + + return buf; +} + #ifndef BLACKBOX_PRINT_HEADER_LINE #define BLACKBOX_PRINT_HEADER_LINE(name, format, ...) case __COUNTER__: \ blackboxPrintfHeaderLine(name, format, __VA_ARGS__); \ @@ -1171,12 +1188,14 @@ static bool blackboxWriteSysinfo(void) return false; } + char buf[FORMATTED_DATE_TIME_BUFSIZE]; + const controlRateConfig_t *currentControlRateProfile = controlRateProfiles(systemConfig()->activeRateProfile); switch (xmitState.headerIndex) { BLACKBOX_PRINT_HEADER_LINE("Firmware type", "%s", "Cleanflight"); BLACKBOX_PRINT_HEADER_LINE("Firmware revision", "%s %s (%s) %s", FC_FIRMWARE_NAME, FC_VERSION_STRING, shortGitRevision, targetName); BLACKBOX_PRINT_HEADER_LINE("Firmware date", "%s %s", buildDate, buildTime); - BLACKBOX_PRINT_HEADER_LINE("Log start datetime", "%s", blackboxGetStartDateTime()); + BLACKBOX_PRINT_HEADER_LINE("Log start datetime", "%s", blackboxGetStartDateTime(buf)); BLACKBOX_PRINT_HEADER_LINE("Craft name", "%s", pilotConfig()->name); BLACKBOX_PRINT_HEADER_LINE("I interval", "%d", blackboxIInterval); BLACKBOX_PRINT_HEADER_LINE("P interval", "%d/%d", blackboxGetRateNum(), blackboxGetRateDenom()); @@ -1648,22 +1667,6 @@ void blackboxUpdate(timeUs_t currentTimeUs) } } -/* - * Returns start time in ISO 8601 format, YYYY-MM-DDThh:mm:ss - * Year value of "0000" indicates time not set - */ -static char startDateTime[20] = "0000-01-01T00:00:00"; -const char *blackboxGetStartDateTime(void) -{ - return startDateTime; -} - -void blackboxSetStartDateTime(const char *dateTime, timeMs_t timeNowMs) -{ - (void)dateTime; - (void)timeNowMs; -} - int blackboxCalculatePDenom(int rateNum, int rateDenom) { return blackboxIInterval * rateNum / rateDenom; diff --git a/src/main/blackbox/blackbox.h b/src/main/blackbox/blackbox.h index b71b0ba42..c6ceac30c 100644 --- a/src/main/blackbox/blackbox.h +++ b/src/main/blackbox/blackbox.h @@ -47,7 +47,6 @@ void blackboxLogEvent(FlightLogEvent event, flightLogEventData_t *data); void blackboxInit(void); void blackboxUpdate(timeUs_t currentTimeUs); -const char *blackboxGetStartDateTime(void); void blackboxSetStartDateTime(const char *dateTime, timeMs_t timeNowMs); int blackboxCalculatePDenom(int rateNum, int rateDenom); uint8_t blackboxGetRateNum(void); diff --git a/src/main/common/time.c b/src/main/common/time.c new file mode 100644 index 000000000..f7003af86 --- /dev/null +++ b/src/main/common/time.c @@ -0,0 +1,250 @@ +/* + * 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 + */ + +#include + +#include "common/maths.h" +#include "common/printf.h" +#include "common/time.h" + +#include "config/parameter_group_ids.h" + +#include "drivers/time.h" + +#ifdef USE_RTC_TIME + +#define UNIX_REFERENCE_YEAR 1970 +#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 - UNIX_REFERENCE_YEAR; // 0-99 + int32_t unixTime = (((year / 4 * (365 * 4 + 1) + days[year % 4][month] + day) * 24 + hour) * 60 + minute) * 60 + second; + return rtcTimeMake(unixTime, dt->millis); +} + +static void rtcTimeToDateTime(dateTime_t *dt, rtcTime_t t) +{ + int32_t unixTime = t / MILLIS_PER_SECOND; + 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 + UNIX_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 >= UNIX_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) +{ + 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; + } + + // 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); +} + +bool dateTimeFormatLocal(char *buf, dateTime_t *dt) +{ + return dateTimeFormat(buf, dt, timeConfig()->tz_offsetMinutes); +} + +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); +} + +#endif \ No newline at end of file diff --git a/src/main/common/time.h b/src/main/common/time.h index 4b32710ba..d858153b8 100644 --- a/src/main/common/time.h +++ b/src/main/common/time.h @@ -17,10 +17,13 @@ #pragma once +#include #include #include "platform.h" +#include "config/parameter_group.h" + // time difference, 32 bits always sufficient typedef int32_t timeDelta_t; // millisecond time @@ -35,3 +38,57 @@ typedef uint32_t timeUs_t; #endif static inline timeDelta_t cmpTimeUs(timeUs_t a, timeUs_t b) { return (timeDelta_t)(a - b); } + +#define FORMATTED_DATE_TIME_BUFSIZE 30 + +#ifdef USE_RTC_TIME + +typedef struct timeConfig_s { + int16_t tz_offsetMinutes; // Offset from UTC in minutes, might be positive or negative +} timeConfig_t; + +PG_DECLARE(timeConfig_t, timeConfig); + +// Milliseconds since Jan 1 1970 +typedef int64_t rtcTime_t; + +rtcTime_t rtcTimeMake(int32_t secs, uint16_t millis); +int32_t rtcTimeGetSeconds(rtcTime_t *t); +uint16_t rtcTimeGetMillis(rtcTime_t *t); + +typedef struct _dateTime_s { + // full year + uint16_t year; + // 1-12 + uint8_t month; + // 1-31 + uint8_t day; + // 0-23 + uint8_t hours; + // 0-59 + uint8_t minutes; + // 0-59 + uint8_t seconds; + // 0-999 + uint16_t millis; +} dateTime_t; + +// buf must be at least FORMATTED_DATE_TIME_BUFSIZE +bool dateTimeFormatUTC(char *buf, dateTime_t *dt); +bool dateTimeFormatLocal(char *buf, dateTime_t *dt); + +void dateTimeUTCToLocal(dateTime_t *utcDateTime, dateTime_t *localDateTime); +// dateTimeSplitFormatted splits a formatted date into its date +// and time parts. Note that the string pointed by formatted will +// be modifed and will become invalid after calling this function. +bool dateTimeSplitFormatted(char *formatted, char **date, char **time); + +bool rtcHasTime(void); + +bool rtcGet(rtcTime_t *t); +bool rtcSet(rtcTime_t *t); + +bool rtcGetDateTime(dateTime_t *dt); +bool rtcSetDateTime(dateTime_t *dt); + +#endif \ No newline at end of file diff --git a/src/main/config/parameter_group_ids.h b/src/main/config/parameter_group_ids.h index 5fbd7dab1..d525e2723 100644 --- a/src/main/config/parameter_group_ids.h +++ b/src/main/config/parameter_group_ids.h @@ -116,7 +116,8 @@ #define PG_FRSKY_D_CONFIG 523 #define PG_MAX7456_CONFIG 524 #define PG_FLYSKY_CONFIG 525 -#define PG_BETAFLIGHT_END 525 +#define PG_TIME_CONFIG 526 +#define PG_BETAFLIGHT_END 526 // OSD configuration (subject to change) diff --git a/src/main/fc/cli.c b/src/main/fc/cli.c index 5e8f2a5f4..620eb767f 100755 --- a/src/main/fc/cli.c +++ b/src/main/fc/cli.c @@ -24,6 +24,7 @@ #include #include "platform.h" +#include "common/time.h" // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled // signal that we're in cli mode @@ -2958,6 +2959,15 @@ static void cliStatus(char *cmdline) UNUSED(cmdline); cliPrintLinef("System Uptime: %d seconds", millis() / 1000); + + #ifdef USE_RTC_TIME + char buf[FORMATTED_DATE_TIME_BUFSIZE]; + dateTime_t dt; + rtcGetDateTime(&dt); + dateTimeFormatLocal(buf, &dt); + cliPrintLinef("Current Time: %s", buf); + #endif + cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString()); cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000)); @@ -3689,6 +3699,7 @@ const clicmd_t cmdTable[] = { CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx), #endif }; + static void cliHelp(char *cmdline) { UNUSED(cmdline); diff --git a/src/main/fc/fc_msp.c b/src/main/fc/fc_msp.c index 7cfdbe5f0..c8315d48c 100644 --- a/src/main/fc/fc_msp.c +++ b/src/main/fc/fc_msp.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "platform.h" @@ -1957,6 +1958,22 @@ static mspResult_e mspProcessInCommand(uint8_t cmdMSP, sbuf_t *src) } break; +#ifdef USE_RTC_TIME + case MSP_SET_RTC: + { + dateTime_t dt; + dt.year = sbufReadU16(src); + dt.month = sbufReadU8(src); + dt.day = sbufReadU8(src); + dt.hours = sbufReadU8(src); + dt.minutes = sbufReadU8(src); + dt.seconds = sbufReadU8(src); + dt.millis = 0; + rtcSetDateTime(&dt); + } + break; +#endif + default: // we do not know how to handle the (valid) message, indicate error MSP $M! return MSP_RESULT_ERROR; diff --git a/src/main/io/asyncfatfs/asyncfatfs.c b/src/main/io/asyncfatfs/asyncfatfs.c index 4400f4dc4..17d08198a 100644 --- a/src/main/io/asyncfatfs/asyncfatfs.c +++ b/src/main/io/asyncfatfs/asyncfatfs.c @@ -29,6 +29,7 @@ #include "fat_standard.h" #include "drivers/sdcard.h" #include "common/maths.h" +#include "common/time.h" #ifdef AFATFS_DEBUG #define ONLY_EXPOSE_FOR_TESTING @@ -2600,10 +2601,25 @@ static void afatfs_createFileContinue(afatfsFile_t *file) memcpy(entry->filename, opState->filename, FAT_FILENAME_LENGTH); entry->attrib = file->attrib; - entry->creationDate = AFATFS_DEFAULT_FILE_DATE; - entry->creationTime = AFATFS_DEFAULT_FILE_TIME; - entry->lastWriteDate = AFATFS_DEFAULT_FILE_DATE; - entry->lastWriteTime = AFATFS_DEFAULT_FILE_TIME; + + uint16_t fileDate = AFATFS_DEFAULT_FILE_DATE; + uint16_t fileTime = AFATFS_DEFAULT_FILE_TIME; + + #ifdef USE_RTC_TIME + // rtcGetDateTime will fill dt with 0000-01-01T00:00:00 + // when time is not known. + dateTime_t dt; + rtcGetDateTime(&dt); + if (dt.year != 0) { + fileDate = FAT_MAKE_DATE(dt.year, dt.month, dt.day); + fileTime = FAT_MAKE_TIME(dt.hours, dt.minutes, dt.seconds); + } + #endif + + entry->creationDate = fileDate; + entry->creationTime = fileTime; + entry->lastWriteDate = fileDate; + entry->lastWriteTime = fileTime; #ifdef AFATFS_DEBUG_VERBOSE fprintf(stderr, "Adding directory entry for %.*s to sector %u\n", FAT_FILENAME_LENGTH, opState->filename, file->directoryEntryPos.sectorNumberPhysical); diff --git a/src/main/target/OMNIBUS/target.h b/src/main/target/OMNIBUS/target.h index 9e6ef8fe7..465592372 100644 --- a/src/main/target/OMNIBUS/target.h +++ b/src/main/target/OMNIBUS/target.h @@ -22,6 +22,7 @@ #undef TELEMETRY_JETIEXBUS // no space left #undef TELEMETRY_MAVLINK // no space left #undef USE_RCDEVICE // no space left +#undef USE_RTC_TIME // no space left #define TARGET_BOARD_IDENTIFIER "OMNI" // https://en.wikipedia.org/wiki/Omnibus diff --git a/src/main/target/SPRACINGF3EVO/target.h b/src/main/target/SPRACINGF3EVO/target.h index 7afc98c44..9845811b0 100644 --- a/src/main/target/SPRACINGF3EVO/target.h +++ b/src/main/target/SPRACINGF3EVO/target.h @@ -28,7 +28,7 @@ #define TARGET_CONFIG #ifdef AIORACERF3 -#undef TARGET_CONFIG +#undef TARGET_CONFIG // no space left #endif #ifdef SPRACINGF3MQ @@ -38,11 +38,12 @@ #define SPRACINGF3MQ_REV 2 #endif -#undef USE_UNCOMMON_MIXERS +#undef USE_UNCOMMON_MIXERS // no space left #endif -#undef TELEMETRY_JETIEXBUS -#undef USE_SERIALRX_JETIEXBUS -#undef USE_DASHBOARD +#undef TELEMETRY_JETIEXBUS // no space left +#undef USE_SERIALRX_JETIEXBUS // no space left +#undef USE_DASHBOARD // no space left +#undef USE_RTC_TIME // no space left #define CONFIG_FASTLOOP_PREFERRED_ACC ACC_DEFAULT @@ -189,5 +190,3 @@ #else #define USED_TIMERS (TIM_N(1) | TIM_N(2) | TIM_N(3) | TIM_N(8) | TIM_N(15) | TIM_N(16)) #endif - -#undef USE_DASHBOARD diff --git a/src/main/target/common_fc_pre.h b/src/main/target/common_fc_pre.h index 8a9ff70ed..8e970c772 100644 --- a/src/main/target/common_fc_pre.h +++ b/src/main/target/common_fc_pre.h @@ -128,6 +128,7 @@ #define USE_HUFFMAN #define USE_COPY_PROFILE_CMS_MENU #define USE_MSP_OVER_TELEMETRY +#define USE_RTC_TIME #ifdef USE_SERIALRX_SPEKTRUM #define USE_SPEKTRUM_BIND