diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs
index 5c6a40d6..aefd1c7b 100644
--- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs
+++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs
@@ -2273,6 +2273,9 @@ namespace IEC61850
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedServer_setRCBEventHandler(IntPtr self, InternalRCBEventHandler handler, IntPtr parameter);
+ [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
+ static extern void IedServer_setTimeQuality(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool leapSecondKnown, [MarshalAs(UnmanagedType.I1)] bool clockFailure, [MarshalAs(UnmanagedType.I1)] bool clockNotSynchronized, int subsecondPrecision);
+
private IntPtr self = IntPtr.Zero;
private InternalControlHandler internalControlHandlerRef = null;
@@ -2996,6 +2999,18 @@ namespace IEC61850
}
}
+ ///
+ /// Set the time quality for all timestamps internally generated by this IedServer instance
+ ///
+ /// set/unset leap seconds known flag
+ /// set/unset clock failure flag
+ /// set/unset clock not synchronized flag
+ /// set the subsecond precision (number of significant bits of the fractionOfSecond part of the time stamp)
+ public void SetTimeQuality(bool leapSecondKnown, bool clockFailure, bool clockNotSynchronized, int subsecondPrecision)
+ {
+ IedServer_setTimeQuality(self, leapSecondKnown, clockFailure, clockNotSynchronized, subsecondPrecision);
+ }
+
}
}
diff --git a/examples/server_example_setting_groups/server_example_sg.c b/examples/server_example_setting_groups/server_example_sg.c
index 4466c1ad..48f8c4d5 100644
--- a/examples/server_example_setting_groups/server_example_sg.c
+++ b/examples/server_example_setting_groups/server_example_sg.c
@@ -97,7 +97,10 @@ readAccessHandler(LogicalDevice* ld, LogicalNode* ln, DataObject* dataObject, Fu
{
void* securityToken = ClientConnection_getSecurityToken(connection);
- printf("Read access to %s/%s.%s\n", ld->name, ln->name, dataObject->name);
+ if (dataObject)
+ printf("Read access to %s/%s.%s[%s]\n", ld->name, ln->name, dataObject->name, FunctionalConstraint_toString(fc));
+ else
+ printf("Read access to %s/%s[%s]\n", ld->name, ln->name, FunctionalConstraint_toString(fc));
return DATA_ACCESS_ERROR_SUCCESS;
}
@@ -112,6 +115,8 @@ main(int argc, char** argv)
iedServer = IedServer_createWithConfig(&iedModel, NULL, config);
+ IedServer_setTimeQuality(iedServer, true, false, false, 10);
+
IedServerConfig_destroy(config);
LogicalDevice* ld = IEDMODEL_PROT;
diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h
index b0097e3f..d206b904 100644
--- a/src/iec61850/inc/iec61850_server.h
+++ b/src/iec61850/inc/iec61850_server.h
@@ -687,6 +687,21 @@ IedServer_setGooseInterfaceIdEx(IedServer self, LogicalNode* ln, const char* gcb
LIB61850_API void
IedServer_useGooseVlanTag(IedServer self, LogicalNode* ln, const char* gcbName, bool useVlanTag);
+/**
+ * \brief Set the time quality for all timestamps internally generated by this IedServer instance
+ *
+ * You can call this function during the initialization of the server or whenever a time quality
+ * flag has to be updated (on clock failure or change of time synchronization state).
+ *
+ * \param self the instance of IedServer to operate on.
+ * \param leapSecondKnown set/unset leap seconds known flag
+ * \param clockFailure set/unset clock failure flag
+ * \param clockNotSynchronized set/unset clock not synchronized flag
+ * \param subsecondPrecision set the subsecond precision (number of significant bits of the fractionOfSecond part of the time stamp)
+ */
+LIB61850_API void
+IedServer_setTimeQuality(IedServer self, bool leapSecondKnown, bool clockFailure, bool clockNotSynchronized, int subsecondPrecision);
+
/**@}*/
/**
@@ -710,8 +725,6 @@ IedServer_useGooseVlanTag(IedServer self, LogicalNode* ln, const char* gcbName,
LIB61850_API void
IedServer_setAuthenticator(IedServer self, AcseAuthenticator authenticator, void* authenticatorParameter);
-
-
/**
* \brief get the peer address of this connection as string
*
diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h
index 14a694b1..e6f6e412 100644
--- a/src/iec61850/inc_private/ied_server_private.h
+++ b/src/iec61850/inc_private/ied_server_private.h
@@ -78,6 +78,8 @@ struct sIedServer
uint8_t edition;
+ uint8_t timeQuality; /* user settable time quality for internally updated times */
+
bool running;
};
diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c
index 70527812..9416d200 100644
--- a/src/iec61850/server/impl/ied_server.c
+++ b/src/iec61850/server/impl/ied_server.c
@@ -659,6 +659,7 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio
}
#endif
+ IedServer_setTimeQuality(self, true, false, false, 10);
}
else {
IedServer_destroy(self);
@@ -1479,7 +1480,7 @@ IedServer_updateUTCTimeAttributeValue(IedServer self, DataAttribute* dataAttribu
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(self->dataModelLock);
#endif
- MmsValue_setUtcTimeMs(dataAttribute->mmsValue, value);
+ MmsValue_setUtcTimeMsEx(dataAttribute->mmsValue, value, self->timeQuality);
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_post(self->dataModelLock);
#endif
@@ -1882,7 +1883,6 @@ private_IedServer_removeClientConnection(IedServer self, ClientConnection client
#endif
}
-
void
IedServer_setGooseInterfaceId(IedServer self, const char* interfaceId)
{
@@ -1890,3 +1890,22 @@ IedServer_setGooseInterfaceId(IedServer self, const char* interfaceId)
self->mmsMapping->gooseInterfaceId = StringUtils_copyString(interfaceId);
#endif
}
+
+void
+IedServer_setTimeQuality(IedServer self, bool leapSecondKnown, bool clockFailure, bool clockNotSynchronized, int subsecondPrecision)
+{
+ uint8_t timeQuality = 0;
+
+ if (clockNotSynchronized)
+ timeQuality += 0x20;
+
+ if (clockFailure)
+ timeQuality += 0x40;
+
+ if (leapSecondKnown)
+ timeQuality += 0x80;
+
+ timeQuality += (subsecondPrecision & 0x1f);
+
+ self->timeQuality = timeQuality;
+}
diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c
index dc2ae540..ba7c5a64 100644
--- a/src/iec61850/server/mms_mapping/control.c
+++ b/src/iec61850/server/mms_mapping/control.c
@@ -258,7 +258,7 @@ copyControlValuesToTrackingObject(MmsMapping* self, ControlObject* controlObject
MmsValue_update(trkInst->ctlNum->mmsValue, controlObject->ctlNum);
if (trkInst->operTm)
- MmsValue_setUtcTimeMs(trkInst->operTm->mmsValue, controlObject->operateTime);
+ MmsValue_setUtcTimeMsEx(trkInst->operTm->mmsValue, controlObject->operateTime, self->iedServer->timeQuality);
if (trkInst->respAddCause)
MmsValue_update(trkInst->respAddCause->mmsValue, controlObject->addCause);
@@ -394,7 +394,7 @@ updateGenericTrackingObjectValues(MmsMapping* self, ControlObject* controlObject
MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType);
if (trkInst->t)
- MmsValue_setUtcTimeMs(trkInst->t->mmsValue, Hal_getTimeInMs());
+ MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), self->iedServer->timeQuality);
if (trkInst->errorCode)
MmsValue_setInt32(trkInst->errorCode->mmsValue, errVal);
@@ -590,9 +590,7 @@ setOpOk(ControlObject* self, bool value, uint64_t currentTimeInMs)
if (self->tOpOk) {
MmsValue* timestamp = self->tOpOk->mmsValue;
- MmsValue_setUtcTimeMs(timestamp, currentTimeInMs);
-
- /* TODO update time quality */
+ MmsValue_setUtcTimeMsEx(timestamp, currentTimeInMs, self->iedServer->timeQuality);
}
self->pendingEvents |= PENDING_EVENT_OP_OK_TRUE;
@@ -862,6 +860,7 @@ executeStateMachine:
MmsValue* operTm = getOperParameterOperTime(controlObject->oper);
MmsValue_setUtcTime(operTm, 0);
+ MmsValue_setUtcTimeQuality(operTm, self->iedServer->timeQuality);
}
else {
MmsServerConnection_sendWriteResponse(controlObject->mmsConnection, controlObject->operateInvokeId,
diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c
index 6fc8f7b4..e841feb2 100644
--- a/src/iec61850/server/mms_mapping/logging.c
+++ b/src/iec61850/server/mms_mapping/logging.c
@@ -418,7 +418,7 @@ updateGenericTrackingObjectValues(MmsMapping* self, LogControl* logControl, IEC6
MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType);
if (trkInst->t)
- MmsValue_setUtcTimeMs(trkInst->t->mmsValue, Hal_getTimeInMs());
+ MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), self->iedServer->timeQuality);
if (trkInst->errorCode)
MmsValue_setInt32(trkInst->errorCode->mmsValue,
diff --git a/src/iec61850/server/mms_mapping/mms_goose.c b/src/iec61850/server/mms_mapping/mms_goose.c
index a5352b00..5aab2585 100644
--- a/src/iec61850/server/mms_mapping/mms_goose.c
+++ b/src/iec61850/server/mms_mapping/mms_goose.c
@@ -1,7 +1,7 @@
/*
* mms_goose.c
*
- * Copyright 2013-2021 Michael Zillgith
+ * Copyright 2013-2022 Michael Zillgith
*
* This file is part of libIEC61850.
*
@@ -231,7 +231,7 @@ updateGenericTrackingObjectValues(MmsGooseControlBlock gc, IEC61850_ServiceType
MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType);
if (trkInst->t)
- MmsValue_setUtcTimeMs(trkInst->t->mmsValue, Hal_getTimeInMs());
+ MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), gc->mmsMapping->iedServer->timeQuality);
if (trkInst->errorCode)
MmsValue_setInt32(trkInst->errorCode->mmsValue,
diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c
index 3a716a27..fc080ea2 100644
--- a/src/iec61850/server/mms_mapping/mms_mapping.c
+++ b/src/iec61850/server/mms_mapping/mms_mapping.c
@@ -693,7 +693,7 @@ updateGenericTrackingObjectValues(MmsMapping* self, SettingGroupControlBlock* sg
MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType);
if (trkInst->t)
- MmsValue_setUtcTimeMs(trkInst->t->mmsValue, Hal_getTimeInMs());
+ MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), self->iedServer->timeQuality);
if (trkInst->errorCode)
MmsValue_setInt32(trkInst->errorCode->mmsValue,
@@ -840,7 +840,7 @@ MmsMapping_changeActiveSettingGroup(MmsMapping* self, SettingGroupControlBlock*
MmsValue* lActTm = MmsValue_getElement(sg->sgcbMmsValues, 4);
MmsValue_setUint8(actSg, sgcb->actSG);
- MmsValue_setUtcTimeMs(lActTm, Hal_getTimeInMs());
+ MmsValue_setUtcTimeMsEx(lActTm, Hal_getTimeInMs(), self->iedServer->timeQuality);
#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
copySGCBValuesToTrackingObject(self, sgcb);
@@ -2720,7 +2720,7 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
MmsValue* lActTm = MmsValue_getElement(sg->sgcbMmsValues, 4);
MmsValue_setUint8(actSg, sg->sgcb->actSG);
- MmsValue_setUtcTimeMs(lActTm, Hal_getTimeInMs());
+ MmsValue_setUtcTimeMsEx(lActTm, Hal_getTimeInMs(), self->iedServer->timeQuality);
}
else
retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c
index c44c2791..3b170a54 100644
--- a/src/iec61850/server/mms_mapping/reporting.c
+++ b/src/iec61850/server/mms_mapping/reporting.c
@@ -579,7 +579,7 @@ updateGenericTrackingObjectValues(MmsMapping* self, ReportControl* rc, IEC61850_
MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType);
if (trkInst->t)
- MmsValue_setUtcTimeMs(trkInst->t->mmsValue, Hal_getTimeInMs());
+ MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), self->iedServer->timeQuality);
if (trkInst->errorCode)
MmsValue_setInt32(trkInst->errorCode->mmsValue,
diff --git a/src/mms/inc/mms_value.h b/src/mms/inc/mms_value.h
index e67bccb2..206345c4 100644
--- a/src/mms/inc/mms_value.h
+++ b/src/mms/inc/mms_value.h
@@ -498,12 +498,30 @@ MmsValue_getUtcTimeInMsWithUs(const MmsValue* self, uint32_t* usec);
* bit 0-4 = subsecond time accuracy (number of significant bits of subsecond time)
*
* \param self MmsValue instance to operate on. Has to be of a type MMS_UTCTIME.
- *
* \param timeQuality the byte representing the time quality
*/
LIB61850_API void
MmsValue_setUtcTimeQuality(MmsValue* self, uint8_t timeQuality);
+/**
+ * \brief Update an MmsValue object of type MMS_UTCTIME with a millisecond time.
+ *
+ * Meaning of the bits in the timeQuality byte:
+ *
+ * bit 7 = leapSecondsKnown
+ * bit 6 = clockFailure
+ * bit 5 = clockNotSynchronized
+ * bit 0-4 = subsecond time accuracy (number of significant bits of subsecond time)
+ *
+ * \param self MmsValue instance to operate on. Has to be of a type MMS_UTCTIME.
+ * \param timeval the new value in milliseconds since epoch (1970/01/01 00:00 UTC)
+ * \param timeQuality the byte representing the time quality
+ *
+ * \return the updated MmsValue instance
+ */
+LIB61850_API MmsValue*
+MmsValue_setUtcTimeMsEx(MmsValue* self, uint64_t timeval, uint8_t timeQuality);
+
/**
* \brief get the TimeQuality byte of the UtcTime
*
diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c
index 5d80a630..29e96e4d 100644
--- a/src/mms/iso_mms/common/mms_value.c
+++ b/src/mms/iso_mms/common/mms_value.c
@@ -763,8 +763,8 @@ MmsValue_setUtcTime(MmsValue* self, uint32_t timeval)
return self;
}
-MmsValue*
-MmsValue_setUtcTimeMs(MmsValue* self, uint64_t timeval)
+static void
+setUtcTimeMs(MmsValue* self, uint64_t timeval, uint8_t timeQuality)
{
uint32_t timeval32 = (timeval / 1000LL);
@@ -786,7 +786,21 @@ MmsValue_setUtcTimeMs(MmsValue* self, uint64_t timeval)
valueArray[6] = (fractionOfSecond & 0xff);
/* encode time quality */
- valueArray[7] = 0x0a; /* 10 bit sub-second time accuracy */
+ valueArray[7] = timeQuality;
+}
+
+MmsValue*
+MmsValue_setUtcTimeMs(MmsValue* self, uint64_t timeval)
+{
+ setUtcTimeMs(self, timeval, 0x0a); /* set quality as 10 bit sub-second time accuracy */
+
+ return self;
+}
+
+MmsValue*
+MmsValue_setUtcTimeMsEx(MmsValue* self, uint64_t timeval, uint8_t timeQuality)
+{
+ setUtcTimeMs(self, timeval, timeQuality);
return self;
}