diff --git a/docs/src/mnem_maps/cfe_time_cmd_mnem_map b/docs/src/mnem_maps/cfe_time_cmd_mnem_map index fc8f2cfbc..641f5008b 100644 --- a/docs/src/mnem_maps/cfe_time_cmd_mnem_map +++ b/docs/src/mnem_maps/cfe_time_cmd_mnem_map @@ -31,4 +31,5 @@ TIME_ADD1HZSTCF=$sc_$cpu_TIME_Add1HzSTCF \ TIME_SUB1HZSTCF=$sc_$cpu_TIME_Sub1HzSTCF \ TIME_STOPADD1HZ=$sc_$cpu_TIME_StopAdd1Hz \ TIME_STOPSUB1HZ=$sc_$cpu_TIME_StopSub1Hz \ -TIME_SETSIGNAL=$sc_$cpu_TIME_SetSignal +TIME_SETSIGNAL=$sc_$cpu_TIME_SetSignal \ +TIME_SETPRINT=$sc_$cpu_TIME_SetPrint diff --git a/modules/core_api/fsw/inc/cfe_error.h b/modules/core_api/fsw/inc/cfe_error.h index bd7138478..6e8a5e3b0 100644 --- a/modules/core_api/fsw/inc/cfe_error.h +++ b/modules/core_api/fsw/inc/cfe_error.h @@ -1362,6 +1362,15 @@ char *CFE_ES_StatusToString(CFE_Status_t status, CFE_StatusString_t *status_stri * */ #define CFE_TIME_BAD_ARGUMENT ((CFE_Status_t)0xce000005) + +/** + * @brief Time Format Production Too Long + * + * The formatting of a time into a string would overflow the + * output buffer length of CFE_TIME_PRINTED_STRING_SIZE. + * + */ +#define CFE_TIME_FORMAT_TOO_LONG ((CFE_Status_t)0xce000006) /**@}*/ #endif /* CFE_ERROR_H */ diff --git a/modules/core_api/fsw/inc/cfe_time_api_typedefs.h b/modules/core_api/fsw/inc/cfe_time_api_typedefs.h index 548a84db1..0055dfd23 100644 --- a/modules/core_api/fsw/inc/cfe_time_api_typedefs.h +++ b/modules/core_api/fsw/inc/cfe_time_api_typedefs.h @@ -42,7 +42,10 @@ */ #define CFE_TIME_PRINTED_STRING_SIZE \ - 24 /**< \brief Required size of buffer to be passed into #CFE_TIME_Print (includes null terminator) */ + 32 /**< \brief Required size of buffer to be passed into #CFE_TIME_Print (includes null terminator) */ + +#define CFE_TIME_FORMAT_SIZE \ + 32 /**< \brief The maximum length we will accept for the format string (incl. null)--affects cmd and tlm */ /*****************************************************************************/ /* diff --git a/modules/time/config/default_cfe_time_extern_typedefs.h b/modules/time/config/default_cfe_time_extern_typedefs.h index 455c1f8af..759a8ede8 100644 --- a/modules/time/config/default_cfe_time_extern_typedefs.h +++ b/modules/time/config/default_cfe_time_extern_typedefs.h @@ -279,4 +279,29 @@ enum CFE_TIME_SetState */ typedef uint8 CFE_TIME_SetState_Enum_t; +enum CFE_TIME_PrintState +{ + /** + * @brief Print timestamp using format string. + */ + CFE_TIME_PrintState_DateTime = 0, + + /** + * @brief Print secs+micros since start/reset. + */ + CFE_TIME_PrintState_SecsSinceStart = 1, + + /** + * @brief Do not print timestamps at all. + */ + CFE_TIME_PrintState_None = 2 +}; + +/** + * @brief Time print status values (how to print timestamps) + * + * @sa enum CFE_TIME_PrintState + */ +typedef uint8 CFE_TIME_PrintState_Enum_t; + #endif /* CFE_TIME_EXTERN_TYPEDEFS_H */ diff --git a/modules/time/config/default_cfe_time_fcncodes.h b/modules/time/config/default_cfe_time_fcncodes.h index 6fe06e1f8..e7f5463ca 100644 --- a/modules/time/config/default_cfe_time_fcncodes.h +++ b/modules/time/config/default_cfe_time_fcncodes.h @@ -689,6 +689,28 @@ ** \sa #CFE_TIME_SET_STATE_CC, #CFE_TIME_SET_SOURCE_CC */ #define CFE_TIME_SET_SIGNAL_CC 15 /* set clock signal (pri vs red) */ + +/** \cfetimecmd Set Print Format Options +** +** \par Description +** This command sets the time print mode/format (used by EVS when sending +** to stdout, and by ES for syslog messages). +** +** \cfecmdmnemonic \TIME_SETPRINT +** +** \par Command Structure +** #CFE_TIME_SetPrintCmd_t +** +** \par Command Verification +** Successful execution of this command may be verified by examining +** housekeeping output from the TIME module. +** +** \par Criticality +** This command is non-critical, usually used in ground systems and +** doing ground tests. +*/ +#define CFE_TIME_SET_PRINT_CC 16 /* set print format command */ + /** \} */ #endif diff --git a/modules/time/config/default_cfe_time_interface_cfg.h b/modules/time/config/default_cfe_time_interface_cfg.h index 151d38c09..dd150afb2 100644 --- a/modules/time/config/default_cfe_time_interface_cfg.h +++ b/modules/time/config/default_cfe_time_interface_cfg.h @@ -198,4 +198,17 @@ */ #define CFE_MISSION_TIME_FS_FACTOR 789004800 +/** + ** \brief On boot, define the time print type. + */ +#define CFE_TIME_PRINT_DEFAULT CFE_TIME_PrintState_DateTime + +/** + ** \brief On boot, the CFE_TIME_Print() function will use + ** the following strftime-like (+ microseconds) format + ** when "printing" times. (Only relevant if CFE_TIME_PRINT_DEFAULT + ** is set to CFE_TIME_PrintState_DateTime.) + */ +#define CFE_TIME_PRINTFMT_DEFAULT "%Y-%j %H:%M:%S.%f" + #endif diff --git a/modules/time/config/default_cfe_time_msgstruct.h b/modules/time/config/default_cfe_time_msgstruct.h index a471f315e..86ff3fff6 100644 --- a/modules/time/config/default_cfe_time_msgstruct.h +++ b/modules/time/config/default_cfe_time_msgstruct.h @@ -97,6 +97,24 @@ typedef struct CFE_TIME_SetStateCmd CFE_TIME_StateCmd_Payload_t Payload; /**< \brief Command payload */ } CFE_TIME_SetStateCmd_t; +/** + * \brief Payload for the command to set the time print format + */ +typedef struct CFE_TIME_SetPrintCmd_Payload +{ + CFE_TIME_PrintState_Enum_t PrintState; + char PrintFormat[CFE_TIME_FORMAT_SIZE]; +} CFE_TIME_SetPrintCmd_Payload_t; + +/** + * \brief Command to set the time print format + */ +typedef struct CFE_TIME_SetPrintCmd +{ + CFE_MSG_CommandHeader_t CommandHeader; /**< \brief Command header */ + CFE_TIME_SetPrintCmd_Payload_t Payload; +} CFE_TIME_SetPrintCmd_t; + /** * \brief Set time data source command payload */ @@ -273,6 +291,9 @@ typedef struct CFE_TIME_HousekeepingTlm_Payload uint32 SubsecsDelay; /**< \cfetlmmnemonic \TIME_1HZDLYSSECS \brief Current 1 Hz SCTF Delay (sub-seconds) */ #endif + + CFE_TIME_PrintState_Enum_t PrintState; + char PrintFormat[CFE_TIME_FORMAT_SIZE]; } CFE_TIME_HousekeepingTlm_Payload_t; typedef struct CFE_TIME_HousekeepingTlm diff --git a/modules/time/fsw/src/cfe_time_api.c b/modules/time/fsw/src/cfe_time_api.c index f8e8b35b6..afdd0071b 100644 --- a/modules/time/fsw/src/cfe_time_api.c +++ b/modules/time/fsw/src/cfe_time_api.c @@ -567,27 +567,78 @@ uint32 CFE_TIME_Micro2SubSecs(uint32 MicroSeconds) *-----------------------------------------------------------------*/ CFE_Status_t CFE_TIME_Print(char *PrintBuffer, CFE_TIME_SysTime_t TimeToPrint) { - size_t FmtLen = 0; - uint32 Micros = (CFE_TIME_Sub2MicroSecs(TimeToPrint.Subseconds) + CFE_MISSION_TIME_EPOCH_MICROS) / 10; + uint32 mic = (CFE_TIME_Sub2MicroSecs(TimeToPrint.Subseconds) + CFE_MISSION_TIME_EPOCH_MICROS) / 10; + time_t sec = TimeToPrint.Seconds + CFE_MISSION_TIME_EPOCH_SECONDS; // epoch is Jan 1, 1980 + /* temporary buffer to store the format so we can modify it to blot '%f' */ + char FmtBuf[CFE_TIME_FORMAT_SIZE]; + char *FmtPtr = FmtBuf; + char *PctF; + size_t OutChrs = 0; struct tm tm; + size_t TimeSz; if (PrintBuffer == NULL) { return CFE_TIME_BAD_ARGUMENT; } - time_t sec = TimeToPrint.Seconds + CFE_MISSION_TIME_EPOCH_SECONDS; // epoch is Jan 1, 1980 - gmtime_r(&sec, &tm); - FmtLen = strftime(PrintBuffer, CFE_TIME_PRINTED_STRING_SIZE - 6, "%Y-%j-%H:%M:%S", &tm); - PrintBuffer += FmtLen; - *(PrintBuffer++) = '.'; - - *(PrintBuffer++) = '0' + (char)((Micros % 100000) / 10000); - *(PrintBuffer++) = '0' + (char)((Micros % 10000) / 1000); - *(PrintBuffer++) = '0' + (char)((Micros % 1000) / 100); - *(PrintBuffer++) = '0' + (char)((Micros % 100) / 10); - *(PrintBuffer++) = '0' + (char)(Micros % 10); - *PrintBuffer = '\0'; + switch (CFE_TIME_Global.PrintState) + { + case CFE_TIME_PrintState_DateTime: + gmtime_r(&sec, &tm); + + strncpy(FmtPtr, CFE_TIME_Global.PrintFormat, CFE_TIME_FORMAT_SIZE); + + while(*FmtPtr != '\0' && OutChrs < CFE_TIME_PRINTED_STRING_SIZE) + { + /* if we have "%f", call strftime for string before and string after */ + PctF = strstr(FmtBuf, "%f"); + if (PctF) + { + *PctF = '\0'; /* blot out "%f", for now */ + } + + TimeSz = strftime(PrintBuffer + OutChrs, CFE_TIME_PRINTED_STRING_SIZE - OutChrs, FmtPtr, &tm); + + if (*FmtPtr && TimeSz == 0) + { + /* strftime returns 0 if the buffer is too small */ + return CFE_TIME_FORMAT_TOO_LONG; + } + + OutChrs += TimeSz; + + /* we found/blotted %f above */ + if (PctF) + { + /* write %f value */ + if (OutChrs < CFE_TIME_PRINTED_STRING_SIZE - 5) + { + OutChrs += snprintf(PrintBuffer + OutChrs, CFE_TIME_PRINTED_STRING_SIZE - OutChrs, "%05d", mic); + } + else + { + return CFE_TIME_FORMAT_TOO_LONG; + } + + /* go back through the loop with the remaining format */ + FmtPtr = PctF + 2; + } + else + { + PrintBuffer[OutChrs] = '\0'; /* just in case, null-terminate the string */ + break; /* break while */ + } + } + break; + case CFE_TIME_PrintState_SecsSinceStart: + OutChrs += snprintf(PrintBuffer, CFE_TIME_PRINTED_STRING_SIZE, "%ld.%06d", (long int)sec, mic); + PrintBuffer[OutChrs] = '\0'; + break; + default: + PrintBuffer[0] = '\0'; + break; + } return CFE_SUCCESS; } diff --git a/modules/time/fsw/src/cfe_time_dispatch.c b/modules/time/fsw/src/cfe_time_dispatch.c index fcd6e08fd..4c261cc46 100644 --- a/modules/time/fsw/src/cfe_time_dispatch.c +++ b/modules/time/fsw/src/cfe_time_dispatch.c @@ -240,6 +240,13 @@ void CFE_TIME_TaskPipe(const CFE_SB_Buffer_t *SBBufPtr) } break; + case CFE_TIME_SET_PRINT_CC: + if (CFE_TIME_VerifyCmdLength(&SBBufPtr->Msg, sizeof(CFE_TIME_SetPrintCmd_t))) + { + CFE_TIME_SetPrintCmd((const CFE_TIME_SetPrintCmd_t *)SBBufPtr); + } + break; + default: CFE_TIME_Global.CommandErrorCounter++; diff --git a/modules/time/fsw/src/cfe_time_task.c b/modules/time/fsw/src/cfe_time_task.c index f392e6627..c57e5cf4a 100644 --- a/modules/time/fsw/src/cfe_time_task.c +++ b/modules/time/fsw/src/cfe_time_task.c @@ -1090,3 +1090,16 @@ int32 CFE_TIME_Sub1HZAdjustmentCmd(const CFE_TIME_Sub1HZAdjustmentCmd_t *data) CFE_TIME_1HzAdjImpl(&data->Payload, CFE_TIME_AdjustDirection_SUBTRACT); return CFE_SUCCESS; } + +/*---------------------------------------------------------------- + * + * Application-scope internal function + * See description in header file for argument/return detail + * + *-----------------------------------------------------------------*/ +int32 CFE_TIME_SetPrintCmd(const CFE_TIME_SetPrintCmd_t *data) +{ + CFE_TIME_Global.PrintState = data->Payload.PrintState; + strncpy(CFE_TIME_Global.PrintFormat, data->Payload.PrintFormat, CFE_TIME_FORMAT_SIZE); + return CFE_SUCCESS; +} diff --git a/modules/time/fsw/src/cfe_time_utils.c b/modules/time/fsw/src/cfe_time_utils.c index d0cdb3bc5..a9d3f8541 100644 --- a/modules/time/fsw/src/cfe_time_utils.c +++ b/modules/time/fsw/src/cfe_time_utils.c @@ -353,6 +353,12 @@ void CFE_TIME_InitData(void) */ CFE_MSG_Init(CFE_MSG_PTR(CFE_TIME_Global.Local1HzCmd.CommandHeader), CFE_SB_ValueToMsgId(CFE_TIME_1HZ_CMD_MID), sizeof(CFE_TIME_Global.Local1HzCmd)); + + /* + ** Configure the default time print format. + */ + CFE_TIME_Global.PrintState = CFE_TIME_PRINT_DEFAULT; + strncpy(CFE_TIME_Global.PrintFormat, CFE_TIME_PRINTFMT_DEFAULT, CFE_TIME_FORMAT_SIZE); } /*---------------------------------------------------------------- @@ -408,6 +414,9 @@ void CFE_TIME_GetHkData(const CFE_TIME_Reference_t *Reference) CFE_TIME_Global.HkPacket.Payload.SecondsDelay = Reference->AtToneDelay.Seconds; CFE_TIME_Global.HkPacket.Payload.SubsecsDelay = Reference->AtToneDelay.Subseconds; #endif + + strncpy(CFE_TIME_Global.HkPacket.Payload.PrintFormat, CFE_TIME_Global.PrintFormat, CFE_TIME_FORMAT_SIZE); + CFE_TIME_Global.HkPacket.Payload.PrintState = CFE_TIME_Global.PrintState; } /*---------------------------------------------------------------- diff --git a/modules/time/fsw/src/cfe_time_utils.h b/modules/time/fsw/src/cfe_time_utils.h index 8c9502bac..b8a93c9fd 100644 --- a/modules/time/fsw/src/cfe_time_utils.h +++ b/modules/time/fsw/src/cfe_time_utils.h @@ -312,6 +312,16 @@ typedef struct ** One callback per app is allowed */ CFE_TIME_SynchCallbackRegEntry_t SynchCallback[CFE_PLATFORM_ES_MAX_APPLICATIONS]; + + /* + ** What form should CFE_TIME_Print produce. + */ + CFE_TIME_PrintState_Enum_t PrintState; + + /* + ** For formatted CFE_TIME_Print output, use this format string. + */ + char PrintFormat[CFE_TIME_FORMAT_SIZE]; } CFE_TIME_Global_t; /* @@ -867,4 +877,10 @@ int32 CFE_TIME_Sub1HZAdjustmentCmd(const CFE_TIME_Sub1HZAdjustmentCmd_t *data); */ int32 CFE_TIME_SubAdjustCmd(const CFE_TIME_SubAdjustCmd_t *data); +/*---------------------------------------------------------------------------------------*/ +/** + * @brief Time task ground command (print format adjust) + */ +int32 CFE_TIME_SetPrintCmd(const CFE_TIME_SetPrintCmd_t *data); + #endif /* CFE_TIME_UTILS_H */ diff --git a/modules/time/ut-coverage/time_UT.c b/modules/time/ut-coverage/time_UT.c index 20161b427..5382bce84 100644 --- a/modules/time/ut-coverage/time_UT.c +++ b/modules/time/ut-coverage/time_UT.c @@ -98,6 +98,8 @@ static const UT_TaskPipeDispatchId_t UT_TPID_CFE_TIME_CMD_SUB_1HZ_ADJUSTMENT_CC static const UT_TaskPipeDispatchId_t UT_TPID_CFE_TIME_INVALID_MID = {.MsgId = CFE_SB_MSGID_RESERVED, .CommandCode = 0}; static const UT_TaskPipeDispatchId_t UT_TPID_CFE_TIME_CMD_INVALID_CC = { .MsgId = CFE_SB_MSGID_WRAP_VALUE(CFE_TIME_CMD_MID), .CommandCode = 0x7F}; +static const UT_TaskPipeDispatchId_t UT_TPID_CFE_TIME_SET_PRINT_CC = { + .MsgId = CFE_SB_MSGID_WRAP_VALUE(CFE_TIME_CMD_MID), .CommandCode = CFE_TIME_SET_PRINT_CC}; /* ** Global variables @@ -906,6 +908,72 @@ void Test_Print(void) UtAssert_MIR("Confirm adding seconds = %u, subseconds = %u to configured EPOCH results in time %s", (unsigned int)time.Seconds, (unsigned int)time.Subseconds, timeBuf); } + + /* Test with different format */ + strcpy(CFE_TIME_Global.PrintFormat, "%Y-%m-%d %H:%M"); + + CFE_UtAssert_SUCCESS(CFE_TIME_Print(timeBuf, time)); + if (usingDefaultEpoch) + { + strcpy(expectedBuf, "2013-01-01 02:03"); + UtAssert_STRINGBUF_EQ(timeBuf, sizeof(timeBuf), expectedBuf, sizeof(expectedBuf)); + } + else + { + UtAssert_MIR("Confirm adding seconds = %u, subseconds = %u to configured EPOCH results in time %s", + (unsigned int)time.Seconds, (unsigned int)time.Subseconds, timeBuf); + } + + /* Test with three milliseconds for the win */ + strcpy(CFE_TIME_Global.PrintFormat, "%f.%f.%f"); + + CFE_UtAssert_SUCCESS(CFE_TIME_Print(timeBuf, time)); + if (usingDefaultEpoch) + { + strcpy(expectedBuf, "49999.49999.49999"); + UtAssert_STRINGBUF_EQ(timeBuf, sizeof(timeBuf), expectedBuf, sizeof(expectedBuf)); + } + else + { + UtAssert_MIR("Confirm adding seconds = %u, subseconds = %u to configured EPOCH results in time %s", + (unsigned int)time.Seconds, (unsigned int)time.Subseconds, timeBuf); + } + + CFE_TIME_Global.PrintState = CFE_TIME_PrintState_SecsSinceStart; + + CFE_UtAssert_SUCCESS(CFE_TIME_Print(timeBuf, time)); + if (usingDefaultEpoch) + { + strcpy(expectedBuf, "49999.49999"); + UtAssert_STRINGBUF_EQ(timeBuf, sizeof(timeBuf), expectedBuf, sizeof(expectedBuf)); + } + else + { + UtAssert_MIR("Confirm adding seconds = %u, subseconds = %u to configured EPOCH results in time %s", + (unsigned int)time.Seconds, (unsigned int)time.Subseconds, timeBuf); + } + + /* Test with too-long of a format */ + strcpy(CFE_TIME_Global.PrintFormat, "%Y%Y%Y%Y%Y%Y%Y%Y%Y%Y%Y"); + CFE_TIME_Global.PrintState = CFE_TIME_PrintState_DateTime; + + UtAssert_INT32_EQ(CFE_TIME_Print(timeBuf, time), CFE_TIME_FORMAT_TOO_LONG); + + /* Test with too-long of a %f format */ + strcpy(CFE_TIME_Global.PrintFormat, "012345678901234567890123456%f"); + + UtAssert_INT32_EQ(CFE_TIME_Print(timeBuf, time), CFE_TIME_FORMAT_TOO_LONG); + + /* Test with "none" option */ + CFE_TIME_Global.PrintState = CFE_TIME_PrintState_None; + CFE_UtAssert_SUCCESS(CFE_TIME_Print(timeBuf, time)); + UtAssert_STRINGBUF_EQ(timeBuf, sizeof(timeBuf), "", 1); + + /* Test with DateTime option and empty format */ + CFE_TIME_Global.PrintState = CFE_TIME_PrintState_DateTime; + strcpy(CFE_TIME_Global.PrintFormat, ""); + CFE_UtAssert_SUCCESS(CFE_TIME_Print(timeBuf, time)); + UtAssert_STRINGBUF_EQ(timeBuf, sizeof(timeBuf), "", 1); } /* @@ -1331,6 +1399,7 @@ void Test_PipeCmds(void) CFE_TIME_SubAdjustCmd_t subadjcmd; CFE_TIME_Add1HZAdjustmentCmd_t add1hzadjcmd; CFE_TIME_Sub1HZAdjustmentCmd_t sub1hzadjcmd; + CFE_TIME_SetPrintCmd_t setprcmd; } CmdBuf; UT_SoftwareBusSnapshot_Entry_t LocalSnapshotData = {.MsgId = CFE_SB_MSGID_WRAP_VALUE(CFE_TIME_HK_TLM_MID)}; @@ -1928,6 +1997,17 @@ void Test_PipeCmds(void) UT_InitData(); UT_CallTaskPipe(CFE_TIME_TaskPipe, &CmdBuf.message, sizeof(CmdBuf.onehzcmd), UT_TPID_CFE_TIME_1HZ_CMD); UtAssert_NONZERO(UT_GetStubCount(UT_KEY(CFE_PSP_GetTime))); + + /* Test sending a print state command */ + UT_InitData(); + memset(&CmdBuf, 0, sizeof(CmdBuf)); + CFE_TIME_Global.PrintState = 0; + strcpy(CFE_TIME_Global.PrintFormat, ""); + CmdBuf.setprcmd.Payload.PrintState = CFE_TIME_PrintState_SecsSinceStart; + strcpy(CmdBuf.setprcmd.Payload.PrintFormat, "abcd"); + UT_CallTaskPipe(CFE_TIME_TaskPipe, &CmdBuf.message, sizeof(CmdBuf.setprcmd), UT_TPID_CFE_TIME_SET_PRINT_CC); + UtAssert_UINT32_EQ(CFE_TIME_Global.PrintState, CFE_TIME_PrintState_SecsSinceStart); + UtAssert_StrCmp(CFE_TIME_Global.PrintFormat, CmdBuf.setprcmd.Payload.PrintFormat, "Print Format %s", CFE_TIME_Global.PrintFormat); } /*