diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index 2c54d75c7e6b06..4fa4a8707b2e81 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -7590,7 +7590,7 @@ endpoint 0 { emits event MissingTrustedTimeSource; callback attribute UTCTime; callback attribute granularity; - ram attribute timeSource default = 0x00; + callback attribute timeSource; callback attribute trustedTimeSource; callback attribute defaultNTP; callback attribute timeZone; diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 683f2dd75658ba..30666d644576ad 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -4915,10 +4915,10 @@ "side": "server", "type": "TimeSourceEnum", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0x00", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/examples/light-switch-app/light-switch-common/light-switch-app.matter b/examples/light-switch-app/light-switch-common/light-switch-app.matter index 65e7c5198a4052..c51aa360992c18 100644 --- a/examples/light-switch-app/light-switch-common/light-switch-app.matter +++ b/examples/light-switch-app/light-switch-common/light-switch-app.matter @@ -2778,7 +2778,7 @@ endpoint 0 { emits event MissingTrustedTimeSource; callback attribute UTCTime; callback attribute granularity; - ram attribute timeSource default = 0x00; + callback attribute timeSource; callback attribute trustedTimeSource; callback attribute defaultNTP; callback attribute timeZone; diff --git a/examples/light-switch-app/light-switch-common/light-switch-app.zap b/examples/light-switch-app/light-switch-common/light-switch-app.zap index dcfa64d7c5e88f..eb25b48af7976a 100644 --- a/examples/light-switch-app/light-switch-common/light-switch-app.zap +++ b/examples/light-switch-app/light-switch-common/light-switch-app.zap @@ -3599,10 +3599,10 @@ "side": "server", "type": "TimeSourceEnum", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0x00", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/src/app/clusters/time-synchronization-server/time-synchronization-server.cpp b/src/app/clusters/time-synchronization-server/time-synchronization-server.cpp index fda3bb50d7fda0..27dae2b8e4dbb6 100644 --- a/src/app/clusters/time-synchronization-server/time-synchronization-server.cpp +++ b/src/app/clusters/time-synchronization-server/time-synchronization-server.cpp @@ -381,12 +381,8 @@ void TimeSynchronizationServer::OnTimeSyncCompletionFn(TimeSourceEnum timeSource } return; } - mGranularity = granularity; - Status status = TimeSource::Set(kRootEndpointId, timeSource); - if (!(status == Status::Success || status == Status::UnsupportedAttribute)) - { - ChipLogError(Zcl, "Writing TimeSource failed."); - } + mGranularity = granularity; + mTimeSource = timeSource; } void TimeSynchronizationServer::OnFallbackNTPCompletionFn(bool timeSyncSuccessful) @@ -395,11 +391,7 @@ void TimeSynchronizationServer::OnFallbackNTPCompletionFn(bool timeSyncSuccessfu { mGranularity = GranularityEnum::kMillisecondsGranularity; // Non-matter SNTP because we know it's external and there's only one source - Status status = TimeSource::Set(kRootEndpointId, TimeSourceEnum::kNonMatterSNTP); - if (!(status == Status::Success || status == Status::UnsupportedAttribute)) - { - ChipLogError(Zcl, "Writing TimeSource failed."); - } + mTimeSource = TimeSourceEnum::kNonMatterSNTP; } else { @@ -438,9 +430,9 @@ void TimeSynchronizationServer::AttemptToGetTime() } } -void TimeSynchronizationServer::Init() +void TimeSynchronizationServer::Init(PersistentStorageDelegate & persistentStorage) { - mTimeSyncDataProvider.Init(Server::GetInstance().GetPersistentStorage()); + mTimeSyncDataProvider.Init(persistentStorage); Structs::TrustedTimeSourceStruct::Type tts; if (mTimeSyncDataProvider.LoadTrustedTimeSource(tts) == CHIP_NO_ERROR) @@ -546,7 +538,7 @@ CHIP_ERROR TimeSynchronizationServer::SetTimeZone(const DataModel::DecodableList } if (items == 0) { - return ClearTimeZone(); + return CHIP_IM_GLOBAL_STATUS(ConstraintError); } char name[TimeSyncDataProvider::kTimeZoneNameLength]; @@ -555,15 +547,18 @@ CHIP_ERROR TimeSynchronizationServer::SetTimeZone(const DataModel::DecodableList if (lastTzState != TimeState::kInvalid) { - const auto & tzStore = GetTimeZone()[0]; - lastTz.offset = tzStore.timeZone.offset; - if (tzStore.timeZone.name.HasValue()) + const TimeSyncDataProvider::TimeZoneStore & tzStore = GetTimeZone()[0]; + lastTz.offset = tzStore.timeZone.offset; + chip::MutableCharSpan tempSpan(name, sizeof(name)); + if (tzStore.timeZone.name.HasValue() && + CHIP_NO_ERROR == CopyCharSpanToMutableCharSpan(tzStore.timeZone.name.Value(), tempSpan)) { - lastTz.name.SetValue(CharSpan(name)); - memcpy(name, tzStore.name, sizeof(tzStore.name)); + lastTz.name.SetValue(CharSpan(tempSpan.data(), tempSpan.size())); } } + CHIP_ERROR err = CHIP_NO_ERROR; + TimeState currentTzState; auto newTzL = tzL.begin(); uint8_t i = 0; InitTimeZone(); @@ -574,20 +569,17 @@ CHIP_ERROR TimeSynchronizationServer::SetTimeZone(const DataModel::DecodableList const auto & newTz = newTzL.GetValue(); if (newTz.offset < -43200 || newTz.offset > 50400) { - ReturnErrorOnFailure(LoadTimeZone()); - return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + ExitNow(err = CHIP_IM_GLOBAL_STATUS(ConstraintError)); } // first element shall have validAt entry of 0 if (i == 0 && newTz.validAt != 0) { - ReturnErrorOnFailure(LoadTimeZone()); - return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + ExitNow(err = CHIP_IM_GLOBAL_STATUS(ConstraintError)); } // if second element, it shall have validAt entry of non-0 if (i != 0 && newTz.validAt == 0) { - ReturnErrorOnFailure(LoadTimeZone()); - return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + ExitNow(err = CHIP_IM_GLOBAL_STATUS(ConstraintError)); } tzStore.timeZone.offset = newTz.offset; tzStore.timeZone.validAt = newTz.validAt; @@ -596,15 +588,13 @@ CHIP_ERROR TimeSynchronizationServer::SetTimeZone(const DataModel::DecodableList size_t len = newTz.name.Value().size(); if (len > sizeof(tzStore.name)) { - ReturnErrorOnFailure(LoadTimeZone()); - return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + ExitNow(err = CHIP_IM_GLOBAL_STATUS(ConstraintError)); } memset(tzStore.name, 0, sizeof(tzStore.name)); chip::MutableCharSpan tempSpan(tzStore.name, len); if (CHIP_NO_ERROR != CopyCharSpanToMutableCharSpan(newTz.name.Value(), tempSpan)) { - ReturnErrorOnFailure(LoadTimeZone()); - return CHIP_IM_GLOBAL_STATUS(InvalidCommand); + ExitNow(err = CHIP_IM_GLOBAL_STATUS(ConstraintError)); } tzStore.timeZone.name.SetValue(CharSpan(tzStore.name, len)); } @@ -616,13 +606,13 @@ CHIP_ERROR TimeSynchronizationServer::SetTimeZone(const DataModel::DecodableList } if (CHIP_NO_ERROR != newTzL.GetStatus()) { - ReturnErrorOnFailure(LoadTimeZone()); - return CHIP_IM_GLOBAL_STATUS(InvalidCommand); + ExitNow(err = CHIP_IM_GLOBAL_STATUS(InvalidCommand)); } mTimeZoneObj.validSize = i; - if (lastTzState != TimeState::kInvalid && TimeState::kInvalid != UpdateTimeZoneState()) + currentTzState = UpdateTimeZoneState(); + if (lastTzState == TimeState::kActive && TimeState::kActive == currentTzState) { bool emit = false; const auto & tz = GetTimeZone()[0].timeZone; @@ -630,16 +620,28 @@ CHIP_ERROR TimeSynchronizationServer::SetTimeZone(const DataModel::DecodableList { emit = true; } - if ((tz.name.HasValue() && lastTz.name.HasValue()) && !(tz.name.Value().data_equal(lastTz.name.Value()))) + if (tz.name.HasValue() != lastTz.name.HasValue() || + ((tz.name.HasValue() && lastTz.name.HasValue()) && !(tz.name.Value().data_equal(lastTz.name.Value())))) { emit = true; } if (emit) { - mEventFlag = TimeSyncEventFlag::kTimeZoneStatus; + emitTimeZoneStatusEvent(GetDelegate()->GetEndpoint()); } } - return mTimeSyncDataProvider.StoreTimeZone(GetTimeZone()); + +exit: + if (err != CHIP_NO_ERROR) + { + // do not propagate if an error occurs here because it is not the user relevant error + LoadTimeZone(); + } + else + { + err = mTimeSyncDataProvider.StoreTimeZone(GetTimeZone()); + } + return err; } CHIP_ERROR TimeSynchronizationServer::LoadTimeZone() @@ -651,7 +653,8 @@ CHIP_ERROR TimeSynchronizationServer::LoadTimeZone() CHIP_ERROR TimeSynchronizationServer::ClearTimeZone() { InitTimeZone(); - return mTimeSyncDataProvider.StoreTimeZone(GetTimeZone()); + ReturnErrorOnFailure(mTimeSyncDataProvider.StoreTimeZone(GetTimeZone())); + return CHIP_NO_ERROR; } void TimeSynchronizationServer::InitDSTOffset() @@ -675,8 +678,10 @@ CHIP_ERROR TimeSynchronizationServer::SetDSTOffset(const DataModel::DecodableLis return ClearDSTOffset(); } - auto newDstL = dstL.begin(); - size_t i = 0; + CHIP_ERROR err = CHIP_NO_ERROR; + uint64_t lastValidUntil = 0; + auto newDstL = dstL.begin(); + size_t i = 0; InitDSTOffset(); while (newDstL.Next()) @@ -686,42 +691,44 @@ CHIP_ERROR TimeSynchronizationServer::SetDSTOffset(const DataModel::DecodableLis i++; } - if (CHIP_NO_ERROR != newDstL.GetStatus()) - { - ReturnErrorOnFailure(LoadDSTOffset()); - return CHIP_IM_GLOBAL_STATUS(InvalidCommand); - } + VerifyOrExit(CHIP_NO_ERROR == newDstL.GetStatus(), err = CHIP_IM_GLOBAL_STATUS(InvalidCommand)); mDstOffsetObj.validSize = i; - // only 1 validuntil null value and shall be last in the list - uint64_t lastValidUntil = 0; for (i = 0; i < mDstOffsetObj.validSize; i++) { - const auto & dstItem = GetDSTOffset()[i]; + const auto & dstItem = mDstOffsetObj.dstOffsetList[i]; // list should be sorted by validStarting // validUntil shall be larger than validStarting if (!dstItem.validUntil.IsNull() && dstItem.validStarting >= dstItem.validUntil.Value()) { - ReturnErrorOnFailure(LoadDSTOffset()); - return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + ExitNow(err = CHIP_IM_GLOBAL_STATUS(ConstraintError)); } // validStarting shall not be smaller than validUntil of previous entry if (dstItem.validStarting < lastValidUntil) { - ReturnErrorOnFailure(LoadDSTOffset()); - return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + ExitNow(err = CHIP_IM_GLOBAL_STATUS(ConstraintError)); } lastValidUntil = !dstItem.validUntil.IsNull() ? dstItem.validUntil.Value() : lastValidUntil; // only 1 validUntil null value and shall be last in the list if (dstItem.validUntil.IsNull() && (i != mDstOffsetObj.validSize - 1)) { - ReturnErrorOnFailure(LoadDSTOffset()); - return CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB; + ExitNow(err = CHIP_IM_GLOBAL_STATUS(ConstraintError)); } } - return mTimeSyncDataProvider.StoreDSTOffset(GetDSTOffset()); +exit: + if (err != CHIP_NO_ERROR) + { + // do not propagate if an error occurs here because it is not the user relevant error + LoadDSTOffset(); + } + else + { + err = mTimeSyncDataProvider.StoreDSTOffset(GetDSTOffset()); + } + + return err; } CHIP_ERROR TimeSynchronizationServer::LoadDSTOffset() @@ -733,8 +740,9 @@ CHIP_ERROR TimeSynchronizationServer::LoadDSTOffset() CHIP_ERROR TimeSynchronizationServer::ClearDSTOffset() { InitDSTOffset(); - ReturnErrorOnFailure(mTimeSyncDataProvider.ClearDSTOffset()); + mDSTOffsetState = TimeState::kInvalid; emitDSTTableEmptyEvent(GetDelegate()->GetEndpoint()); + ReturnErrorOnFailure(mTimeSyncDataProvider.ClearDSTOffset()); return CHIP_NO_ERROR; } @@ -779,13 +787,8 @@ CHIP_ERROR TimeSynchronizationServer::SetUTCTime(EndpointId ep, uint64_t utcTime return err; } GetDelegate()->UTCTimeAvailabilityChanged(utcTime); - mGranularity = granularity; - Status status = TimeSource::Set(ep, source); - if (!(status == Status::Success || status == Status::UnsupportedAttribute)) - { - ChipLogError(Zcl, "Writing TimeSource failed."); - return CHIP_IM_GLOBAL_STATUS(Failure); - } + mGranularity = static_cast(to_underlying(granularity) - 1); + mTimeSource = source; return CHIP_NO_ERROR; } @@ -798,14 +801,11 @@ CHIP_ERROR TimeSynchronizationServer::GetLocalTime(EndpointId ep, DataModel::Nul { return CHIP_ERROR_INVALID_TIME; } - TimeState newState = UpdateDSTOffsetState(); - VerifyOrReturnError(TimeState::kInvalid != newState, CHIP_ERROR_INVALID_TIME); + VerifyOrReturnError(TimeState::kInvalid != UpdateDSTOffsetState(), CHIP_ERROR_INVALID_TIME); ReturnErrorOnFailure(System::SystemClock().GetClock_RealTime(utcTime)); VerifyOrReturnError(UnixEpochToChipEpochMicros(utcTime.count(), chipEpochTime), CHIP_ERROR_INVALID_TIME); - if (TimeState::kChanged == UpdateTimeZoneState()) - { - emitTimeZoneStatusEvent(ep); - } + TimeState tzState = UpdateTimeZoneState(); + VerifyOrReturnError(TimeState::kInvalid != tzState, CHIP_ERROR_INVALID_TIME); VerifyOrReturnError(GetTimeZone().size() != 0, CHIP_ERROR_INVALID_TIME); const auto & tzStore = GetTimeZone()[0]; timeZoneOffset = static_cast(tzStore.timeZone.offset); @@ -821,10 +821,7 @@ CHIP_ERROR TimeSynchronizationServer::GetLocalTime(EndpointId ep, DataModel::Nul uint64_t localTimeSec = static_cast(static_cast(chipEpochTime) + timeZoneOffset + dstOffset); localTime.SetNonNull((localTimeSec * chip::kMicrosecondsPerSecond) + usRemainder); - if (newState == TimeState::kChanged) - { - emitDSTStatusEvent(0, dstOffset != 0); - } + return CHIP_NO_ERROR; } @@ -839,12 +836,13 @@ TimeState TimeSynchronizationServer::UpdateTimeZoneState() // This will be set once we receive a good time either from the delegate or via a command if (mGranularity == GranularityEnum::kNoTimeGranularity) { - return TimeState::kInvalid; + mTimeZoneState = TimeState::kInvalid; + return mTimeZoneState; } - VerifyOrReturnValue(System::SystemClock().GetClock_RealTime(utcTime) == CHIP_NO_ERROR, TimeState::kInvalid); - VerifyOrReturnValue(tzList.size() != 0, TimeState::kInvalid); - VerifyOrReturnValue(UnixEpochToChipEpochMicros(utcTime.count(), chipEpochTime), TimeState::kInvalid); + VerifyOrExit(System::SystemClock().GetClock_RealTime(utcTime) == CHIP_NO_ERROR, mTimeZoneState = TimeState::kInvalid); + VerifyOrExit(tzList.size() != 0, mTimeZoneState = TimeState::kInvalid); + VerifyOrExit(UnixEpochToChipEpochMicros(utcTime.count(), chipEpochTime), mTimeZoneState = TimeState::kInvalid); for (size_t i = 0; i < tzList.size(); i++) { @@ -855,15 +853,22 @@ TimeState TimeSynchronizationServer::UpdateTimeZoneState() activeTzIndex = i; } } - if (activeTzIndex != 0) + + if (activeTzIndex != 0) // bring item to the front of the list { - mTimeZoneObj.validSize = tzList.size() - activeTzIndex; - auto newTimeZoneList = tzList.SubSpan(activeTzIndex); - VerifyOrReturnValue(mTimeSyncDataProvider.StoreTimeZone(newTimeZoneList) == CHIP_NO_ERROR, TimeState::kInvalid); - VerifyOrReturnValue(LoadTimeZone() == CHIP_NO_ERROR, TimeState::kInvalid); - return TimeState::kChanged; + auto newTimeZoneList = tzList.SubSpan(activeTzIndex); + VerifyOrExit(mTimeSyncDataProvider.StoreTimeZone(newTimeZoneList) == CHIP_NO_ERROR, mTimeZoneState = TimeState::kInvalid); + VerifyOrExit(LoadTimeZone() == CHIP_NO_ERROR, mTimeZoneState = TimeState::kInvalid); } - return TimeState::kActive; + if (activeTzIndex || (TimeState::kActive != mTimeZoneState)) + { + emitTimeZoneStatusEvent(GetDelegate()->GetEndpoint()); + } + + mTimeZoneState = TimeState::kActive; + +exit: + return mTimeZoneState; } TimeState TimeSynchronizationServer::UpdateDSTOffsetState() @@ -873,17 +878,19 @@ TimeState TimeSynchronizationServer::UpdateDSTOffsetState() size_t activeDstIndex = 0; uint64_t chipEpochTime; bool dstStopped = true; + TimeState state; // This return allows us to simulate no internal time for testing purposes // This will be set once we receive a good time either from the delegate or via a command if (mGranularity == GranularityEnum::kNoTimeGranularity) { - return TimeState::kInvalid; + mDSTOffsetState = TimeState::kInvalid; + return mDSTOffsetState; } - VerifyOrReturnValue(System::SystemClock().GetClock_RealTime(utcTime) == CHIP_NO_ERROR, TimeState::kInvalid); - VerifyOrReturnValue(dstList.size() != 0, TimeState::kInvalid); - VerifyOrReturnValue(UnixEpochToChipEpochMicros(utcTime.count(), chipEpochTime), TimeState::kInvalid); + VerifyOrExit(System::SystemClock().GetClock_RealTime(utcTime) == CHIP_NO_ERROR, mDSTOffsetState = TimeState::kInvalid); + VerifyOrExit(dstList.size() != 0, mDSTOffsetState = TimeState::kInvalid); + VerifyOrExit(UnixEpochToChipEpochMicros(utcTime.count(), chipEpochTime), mDSTOffsetState = TimeState::kInvalid); for (size_t i = 0; i < dstList.size(); i++) { @@ -893,46 +900,69 @@ TimeState TimeSynchronizationServer::UpdateDSTOffsetState() dstStopped = false; } } - VerifyOrReturnValue(!dstStopped, TimeState::kStopped); - // if offset is zero and validUntil is null then no DST is used - if (dstList[activeDstIndex].offset == 0 && dstList[activeDstIndex].validUntil.IsNull()) + + if (dstStopped) + { + if (mDSTOffsetState != TimeState::kStopped) + { + mDSTOffsetState = TimeState::kStopped; + emitDSTStatusEvent(GetDelegate()->GetEndpoint(), false); + } + return mDSTOffsetState; + } + + if (dstList[activeDstIndex].validUntil.IsNull()) { - return TimeState::kStopped; + if (dstList[activeDstIndex].offset == 0) + { + // no DST is used + state = TimeState::kStopped; + } + else + { + // permanent DST is used + state = TimeState::kActive; + } } - if (!dstList[activeDstIndex].validUntil.IsNull() && dstList[activeDstIndex].validUntil.Value() <= chipEpochTime) + else if (dstList[activeDstIndex].validUntil.Value() <= chipEpochTime) { - if (activeDstIndex + 1 >= mDstOffsetObj.validSize) // last item in the list + if (activeDstIndex + 1 >= mDstOffsetObj.validSize) { - VerifyOrReturnValue(ClearDSTOffset() == CHIP_NO_ERROR, TimeState::kInvalid); + // this is the last item in the list that has already expired + ClearDSTOffset(); + // no DST available return TimeState::kInvalid; } - int32_t previousOffset = dstList[activeDstIndex].offset; - dstList[activeDstIndex].offset = 0; // not using dst and last DST item in the list is not active yet - // TODO: This enum mixes state and transitions in a way that's very confusing. This should return either an active, an - // inactive or an invalid and the caller should make the judgement about whether that has changed OR this function should - // just return a bool indicating whether a change happened - return previousOffset == 0 ? TimeState::kStopped : TimeState::kChanged; + // current DST has expired + // not using DST and next DST item in the list is not active yet + dstList[activeDstIndex].offset = 0; + state = TimeState::kStopped; + } + else + { + state = TimeState::kActive; } + if (activeDstIndex > 0) { - mDstOffsetObj.validSize = dstList.size() - activeDstIndex; - auto newDstOffsetList = dstList.SubSpan(activeDstIndex); + auto newDstOffsetList = dstList.SubSpan(activeDstIndex); VerifyOrReturnValue(mTimeSyncDataProvider.StoreDSTOffset(newDstOffsetList) == CHIP_NO_ERROR, TimeState::kInvalid); VerifyOrReturnValue(LoadDSTOffset() == CHIP_NO_ERROR, TimeState::kInvalid); - return TimeState::kChanged; + // DST could have been in the same state but the DST item has changed. Therefore, emit the event. + if (mDSTOffsetState == state) + { + emitDSTStatusEvent(GetDelegate()->GetEndpoint(), state == TimeState::kActive); + } } - return TimeState::kActive; -} -TimeSyncEventFlag TimeSynchronizationServer::GetEventFlag() -{ - return mEventFlag; -} + if (state != mDSTOffsetState) + { + emitDSTStatusEvent(GetDelegate()->GetEndpoint(), state == TimeState::kActive); + mDSTOffsetState = state; + } -void TimeSynchronizationServer::ClearEventFlag(TimeSyncEventFlag flag) -{ - uint8_t eventFlag = to_underlying(mEventFlag) ^ to_underlying(flag); - mEventFlag = static_cast(eventFlag); +exit: + return mDSTOffsetState; } void TimeSynchronizationServer::OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) @@ -1053,6 +1083,9 @@ CHIP_ERROR TimeSynchronizationAttrAccess::Read(const ConcreteReadAttributePath & case Granularity::Id: { return aEncoder.Encode(TimeSynchronizationServer::Instance().GetGranularity()); } + case TimeSource::Id: { + return aEncoder.Encode(TimeSynchronizationServer::Instance().GetTimeSource()); + } case TrustedTimeSource::Id: { return ReadTrustedTimeSource(aPath.mEndpointId, aEncoder); } @@ -1096,12 +1129,12 @@ bool emberAfTimeSynchronizationClusterSetUTCTimeCallback( auto currentGranularity = TimeSynchronizationServer::Instance().GetGranularity(); if (granularity < GranularityEnum::kNoTimeGranularity || granularity > GranularityEnum::kMicrosecondsGranularity) { - commandObj->AddStatus(commandPath, Status::InvalidCommand); + commandObj->AddStatus(commandPath, Status::ConstraintError); return true; } if (timeSource.HasValue() && (timeSource.Value() < TimeSourceEnum::kNone || timeSource.Value() > TimeSourceEnum::kGnss)) { - commandObj->AddStatus(commandPath, Status::InvalidCommand); + commandObj->AddStatus(commandPath, Status::ConstraintError); return true; } @@ -1132,8 +1165,6 @@ bool emberAfTimeSynchronizationClusterSetTrustedTimeSourceCallback( Structs::TrustedTimeSourceStruct::Type ts = { commandObj->GetAccessingFabricIndex(), timeSource.Value().nodeID, timeSource.Value().endpoint }; tts.SetNonNull(ts); - // TODO: schedule a utctime read from this time source and emit event only on failure to get time - emitTimeFailureEvent(commandPath.mEndpointId); } else { @@ -1170,17 +1201,15 @@ bool emberAfTimeSynchronizationClusterSetTimeZoneCallback( return true; } - if (to_underlying(TimeSynchronizationServer::Instance().GetEventFlag()) & to_underlying(TimeSyncEventFlag::kTimeZoneStatus)) - { - TimeSynchronizationServer::Instance().ClearEventFlag(TimeSyncEventFlag::kTimeZoneStatus); - emitTimeZoneStatusEvent(commandPath.mEndpointId); - } GetDelegate()->TimeZoneListChanged(TimeSynchronizationServer::Instance().GetTimeZone()); TimeZoneDatabaseEnum tzDb; - TimeZoneDatabase::Get(commandPath.mEndpointId, &tzDb); + if (Status::Success != TimeZoneDatabase::Get(commandPath.mEndpointId, &tzDb)) + { + commandObj->AddStatus(commandPath, Status::Failure); + return true; + } Commands::SetTimeZoneResponse::Type response; - TimeSynchronizationServer::Instance().UpdateTimeZoneState(); const auto & tzList = TimeSynchronizationServer::Instance().GetTimeZone(); if (GetDelegate()->HasFeature(Feature::kTimeZone) && tzDb != TimeZoneDatabaseEnum::kNone && tzList.size() != 0) { @@ -1188,7 +1217,8 @@ bool emberAfTimeSynchronizationClusterSetTimeZoneCallback( if (tz.name.HasValue() && GetDelegate()->HandleUpdateDSTOffset(tz.name.Value())) { response.DSTOffsetRequired = false; - emitDSTStatusEvent(commandPath.mEndpointId, true); + // update DST to trigger generating DSTStatus event after a new DST setting + (void) TimeSynchronizationServer::Instance().UpdateDSTOffsetState(); } else { @@ -1202,12 +1232,10 @@ bool emberAfTimeSynchronizationClusterSetTimeZoneCallback( if (response.DSTOffsetRequired) { - TimeState dstState = TimeSynchronizationServer::Instance().UpdateDSTOffsetState(); + // attempt to emit DSTStatus event if DST was previously available + (void) TimeSynchronizationServer::Instance().UpdateDSTOffsetState(); + // clear DSTOffset and emit DSTTableEmpty event TimeSynchronizationServer::Instance().ClearDSTOffset(); - if (dstState == TimeState::kActive || dstState == TimeState::kChanged) - { - emitDSTStatusEvent(commandPath.mEndpointId, false); - } } commandObj->AddResponse(commandPath, response); @@ -1239,11 +1267,12 @@ bool emberAfTimeSynchronizationClusterSetDSTOffsetCallback( } return true; } - // if DST state changes, generate DSTStatus event - if (dstState != TimeSynchronizationServer::Instance().UpdateDSTOffsetState()) + TimeState newDstState = TimeSynchronizationServer::Instance().UpdateDSTOffsetState(); + // if DST state stays in the same state before and after calling SetDSTOffset(), emit DSTStatus event + // because UpdateDSTOffsetState() has no way of knowing that the state has changed + if (dstState == newDstState) { - emitDSTStatusEvent(commandPath.mEndpointId, - TimeState::kActive == TimeSynchronizationServer::Instance().UpdateDSTOffsetState()); + emitDSTStatusEvent(commandPath.mEndpointId, TimeState::kActive == newDstState); } commandObj->AddStatus(commandPath, Status::Success); @@ -1289,6 +1318,6 @@ bool emberAfTimeSynchronizationClusterSetDefaultNTPCallback( void MatterTimeSynchronizationPluginServerInitCallback() { - TimeSynchronizationServer::Instance().Init(); + TimeSynchronizationServer::Instance().Init(Server::GetInstance().GetPersistentStorage()); registerAttributeAccessOverride(&gAttrAccess); } diff --git a/src/app/clusters/time-synchronization-server/time-synchronization-server.h b/src/app/clusters/time-synchronization-server/time-synchronization-server.h index 2581c9712e7862..0285eb6377567f 100644 --- a/src/app/clusters/time-synchronization-server/time-synchronization-server.h +++ b/src/app/clusters/time-synchronization-server/time-synchronization-server.h @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -52,21 +51,7 @@ enum class TimeState : uint8_t { kInvalid = 0, // No valid offset available kActive = 1, // An offset is currently being used - kChanged = 2, // An offset expired or changed to a new value - kStopped = 3, // Permanent item in use -}; - -/** - * @brief Flags for tracking event types to emit. - */ -enum class TimeSyncEventFlag : uint8_t -{ - kNone = 0, - kDSTTableEmpty = 1, - kDSTStatus = 2, - kTimeZoneStatus = 4, - kTimeFailure = 8, - kMissingTTSource = 16, + kStopped = 2, // Permanent item in use }; void SetDefaultDelegate(Delegate * delegate); @@ -80,7 +65,7 @@ class TimeSynchronizationServer : public FabricTable::Delegate { public: TimeSynchronizationServer(); - void Init(); + void Init(PersistentStorageDelegate & persistentStorage); void Shutdown(); static TimeSynchronizationServer & Instance(void); @@ -104,13 +89,28 @@ class TimeSynchronizationServer : public FabricTable::Delegate CHIP_ERROR SetUTCTime(chip::EndpointId ep, uint64_t utcTime, GranularityEnum granularity, TimeSourceEnum source); CHIP_ERROR GetLocalTime(chip::EndpointId ep, DataModel::Nullable & localTime); GranularityEnum & GetGranularity() { return mGranularity; } + TimeSourceEnum & GetTimeSource() { return mTimeSource; } void ScheduleDelayedAction(System::Clock::Seconds32 delay, System::TimerCompleteCallback action, void * aAppState); + /** + * @brief Updates time zone to the correct item from the time zone list. + * If the TimeState has changed from the previous state, the function will emit a TimeZoneStatus event. + * + * Returns kInvalid if an error occurrs. + * Otherwise, it returns kActive since there is always a default time zone available. + * kStopped is not used in this function. + */ TimeState UpdateTimeZoneState(); + /** + * @brief Updates DST offset to the correct item from the DSTOffset list. + * If the DST offset has changed from the previous state or value, the function will emit a DSTStatus event. + * + * Returns kInvalid if an error occurrs or the DSTOffset list is empty. + * Returns kActive if a DST offset is being used. + * Returns kStopped if no DST offset is applicable at the current time. + */ TimeState UpdateDSTOffsetState(); - TimeSyncEventFlag GetEventFlag(void); - void ClearEventFlag(TimeSyncEventFlag flag); // Fabric Table delegate functions void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override; @@ -139,13 +139,15 @@ class TimeSynchronizationServer : public FabricTable::Delegate TimeSyncDataProvider::TimeZoneObj mTimeZoneObj{ Span(mTz), 0 }; TimeSyncDataProvider::DSTOffsetObj mDstOffsetObj{ DataModel::List(mDst), 0 }; GranularityEnum mGranularity = GranularityEnum::kNoTimeGranularity; + TimeSourceEnum mTimeSource = TimeSourceEnum::kNone; TimeSyncDataProvider::TimeZoneStore mTz[CHIP_CONFIG_TIME_ZONE_LIST_MAX_SIZE]; Structs::DSTOffsetStruct::Type mDst[CHIP_CONFIG_DST_OFFSET_LIST_MAX_SIZE]; TimeSyncDataProvider mTimeSyncDataProvider; static TimeSynchronizationServer sTimeSyncInstance; - TimeSyncEventFlag mEventFlag = TimeSyncEventFlag::kNone; + TimeState mTimeZoneState = TimeState::kInvalid; + TimeState mDSTOffsetState = TimeState::kInvalid; #if TIME_SYNC_ENABLE_TSC_FEATURE chip::Callback::Callback mOnDeviceConnectedCallback; chip::Callback::Callback mOnDeviceConnectionFailureCallback; diff --git a/src/app/tests/suites/TestTimeSynchronization.yaml b/src/app/tests/suites/TestTimeSynchronization.yaml index 5f34a1967d90bf..02160442fc105e 100644 --- a/src/app/tests/suites/TestTimeSynchronization.yaml +++ b/src/app/tests/suites/TestTimeSynchronization.yaml @@ -127,15 +127,13 @@ tests: - name: "TimeZone" value: [] response: - values: - - name: "DSTOffsetRequired" - value: true + error: CONSTRAINT_ERROR - label: "Read Time Zone" command: "readAttribute" attribute: "TimeZone" response: - value: [{ Offset: 0, ValidAt: 0 }] + value: [{ Offset: 100, ValidAt: 0 }] - label: "Read DSTOffset" command: "readAttribute" @@ -198,11 +196,7 @@ tests: command: "readAttribute" attribute: "DSTOffset" response: - value: - [ - { Offset: 1, ValidStarting: 1, ValidUntil: 2 }, - { Offset: 0, ValidStarting: 3, ValidUntil: null }, - ] + value: [{ Offset: 0, ValidStarting: 3, ValidUntil: null }] - label: "Set DSTOffset with same validStarting and validUntil" command: "SetDSTOffset" diff --git a/src/app/zap-templates/zcl/zcl-with-test-extensions.json b/src/app/zap-templates/zcl/zcl-with-test-extensions.json index 7213b96cf0e012..53d26098e8a935 100644 --- a/src/app/zap-templates/zcl/zcl-with-test-extensions.json +++ b/src/app/zap-templates/zcl/zcl-with-test-extensions.json @@ -383,7 +383,8 @@ "LocalTime", "Granularity", "TimeZoneListMaxSize", - "DSTOffsetListMaxSize" + "DSTOffsetListMaxSize", + "TimeSource" ], "Temperature Control": ["SupportedTemperatureLevels"], "Dishwasher Mode": [ diff --git a/src/app/zap-templates/zcl/zcl.json b/src/app/zap-templates/zcl/zcl.json index 8a4159e98f7eb8..03d54987075ede 100644 --- a/src/app/zap-templates/zcl/zcl.json +++ b/src/app/zap-templates/zcl/zcl.json @@ -381,7 +381,8 @@ "LocalTime", "Granularity", "TimeZoneListMaxSize", - "DSTOffsetListMaxSize" + "DSTOffsetListMaxSize", + "TimeSource" ], "Temperature Control": ["SupportedTemperatureLevels"], "Dishwasher Mode": [ diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp index 78c93070ca6d03..9d6dd4ef9dd41e 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp @@ -7083,53 +7083,6 @@ Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint16_t valu namespace TimeSynchronization { namespace Attributes { -namespace TimeSource { - -Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::TimeSourceEnum * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - Protocols::InteractionModel::Status status = - emberAfReadAttribute(endpoint, Clusters::TimeSynchronization::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(Protocols::InteractionModel::Status::Success == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - *value = Traits::StorageToWorking(temp); - return status; -} - -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::TimeSourceEnum value, - MarkAttributeDirty markDirty) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::TimeSynchronization::Id, Id, writable, ZCL_ENUM8_ATTRIBUTE_TYPE, markDirty); -} - -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::TimeSourceEnum value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::TimeSynchronization::Id, Id, writable, ZCL_ENUM8_ATTRIBUTE_TYPE); -} - -} // namespace TimeSource - namespace TimeZoneDatabase { Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h index e51fb1999e9ce8..c39d7f0638f3cb 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h @@ -1136,14 +1136,6 @@ Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint16_t valu namespace TimeSynchronization { namespace Attributes { -namespace TimeSource { -Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, - chip::app::Clusters::TimeSynchronization::TimeSourceEnum * value); // TimeSourceEnum -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::TimeSourceEnum value); -Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::TimeSourceEnum value, - MarkAttributeDirty markDirty); -} // namespace TimeSource - namespace TimeZoneDatabase { Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, chip::app::Clusters::TimeSynchronization::TimeZoneDatabaseEnum * value); // TimeZoneDatabaseEnum