blob: 44397897dd8a09c405ab494f6145ca30e14b398e [file] [log] [blame]
/*
*
* Copyright (c) 2025 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "chef-oven-cavity-operational-state.h"
#include <app/util/attribute-storage.h>
#include <app/util/endpoint-config-api.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/CHIPDeviceLayer.h>
#ifdef MATTER_DM_PLUGIN_OVEN_CAVITY_OPERATIONAL_STATE_SERVER
namespace chip {
namespace app {
namespace Clusters {
namespace OvenCavityOperationalState {
constexpr size_t kOvenCavityOperationalStateTableSize = MATTER_DM_OPERATIONAL_STATE_OVEN_CLUSTER_SERVER_ENDPOINT_COUNT;
static_assert(kOvenCavityOperationalStateTableSize < kEmberInvalidEndpointIndex, "OvenCavityOperationalState table size error");
std::unique_ptr<ChefDelegate> gDelegateTable[kOvenCavityOperationalStateTableSize];
std::unique_ptr<Instance> gInstanceTable[kOvenCavityOperationalStateTableSize];
/**
* Timer mechanism -
* 1. Call StartTimer with input of one second and function onOvenCavityOperationalStateTimerTick to start the timer.
* This will call onOvenCavityOperationalStateTimerTick after one second.
* 2. onOvenCavityOperationalStateTimerTick is the timer tick function. It decides whether to run another one second iteration
* (if there are seconds left to run) or just return and end the timer (when state is invalid or cycle is complete).
* Timer usage -
* * Timer is started on receiving start command.
* * Timer continues to run until running time has surpassed cycle time (cycle is complete) OR operational state is non-running.
* * Stop command handler ends an ongoing timer.
*/
static void onOvenCavityOperationalStateTimerTick(System::Layer * systemLayer, void * data)
{
ChefDelegate * delegate = reinterpret_cast<ChefDelegate *>(data);
uint8_t opState = delegate->GetCurrentOperationalState();
if (opState != to_underlying(OperationalStateEnum::kRunning))
{
ChipLogError(DeviceLayer, "onOperationalStateTimerTick: Operational cycle can not be active in state %d", opState);
return;
}
if (!delegate->CheckCycleActive())
return;
delegate->CycleSecondTick();
if (delegate->CheckCycleComplete())
{
OperationalState::GenericOperationalError err(to_underlying(ErrorStateEnum::kNoError));
delegate->HandleStopStateCallback(err);
}
else
{
CHIP_ERROR err =
DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), onOvenCavityOperationalStateTimerTick, delegate);
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "onOvenCavityOperationalStateTimerTick: Failed to start timer. %" CHIP_ERROR_FORMAT,
err.Format());
}
}
}
DataModel::Nullable<uint32_t> ChefDelegate::GetCountdownTime()
{
if (mRunningTime.IsNull() || CheckCycleComplete())
return DataModel::NullNullable;
return DataModel::MakeNullable(kCycleSeconds - mRunningTime.Value());
}
bool ChefDelegate::StartCycle()
{
if (!mRunningTime.IsNull())
{
ChipLogError(DeviceLayer, "StartCycle: Cycle is in progress. Can not start new cycle.");
return false;
}
mRunningTime.SetNonNull(static_cast<uint32_t>(0));
app::DataModel::Nullable<uint8_t> phase = GetRunningPhase();
if (phase.IsNull())
{
ChipLogError(DeviceLayer, "StartCycle: Phase did't update to non-NULL.");
return false;
}
if (phase.Value() != kPreHeatingIndex)
{
ChipLogError(DeviceLayer, "StartCycle: Unexpected start phase: %d.", phase.Value());
return false;
}
CHIP_ERROR err = GetInstance()->SetCurrentPhase(phase);
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "StartCycle: Error setting current phase %" CHIP_ERROR_FORMAT, err.Format());
}
ChipLogDetail(DeviceLayer, "Oven Cycle Started. Current Phase: %d. Cycle time: %d", phase.Value(), kCycleSeconds);
return true;
}
void ChefDelegate::CycleSecondTick()
{
if (mRunningTime.IsNull() || CheckCycleComplete())
return;
uint8_t opState = GetCurrentOperationalState();
DataModel::Nullable<uint8_t> oldPhase = GetRunningPhase();
if (opState == to_underlying(OperationalStateEnum::kRunning))
{
mRunningTime.SetNonNull(mRunningTime.ValueOr(0) + 1);
}
else
{
ChipLogError(DeviceLayer, "CycleSecondTick: Invalid state %d", opState);
return;
}
DataModel::Nullable<uint8_t> newPhase = GetRunningPhase();
if (newPhase != oldPhase)
{
CHIP_ERROR err = GetInstance()->SetCurrentPhase(newPhase);
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "CycleSecondTick: Error setting current phase %" CHIP_ERROR_FORMAT, err.Format());
}
}
GetInstance()->UpdateCountdownTimeFromDelegate();
ChipLogDetail(DeviceLayer, "CycleSecondTick: Running time: %d.", mRunningTime.ValueOr(0));
if (newPhase.IsNull())
{
ChipLogDetail(DeviceLayer, "CycleSecondTick: Phase: NULL");
}
else
{
ChipLogDetail(DeviceLayer, "CycleSecondTick: Phase: %d", newPhase.Value());
}
}
DataModel::Nullable<uint8_t> ChefDelegate::GetRunningPhase()
{
if (mRunningTime.IsNull() || CheckCycleComplete())
return DataModel::NullNullable;
if (mRunningTime.Value() >= kPreHeatingSeconds + kPreHeatedSeconds)
return kCoolingDownIndex;
else if (mRunningTime.Value() >= kPreHeatingSeconds)
return kPreHeatedIndex;
else
return kPreHeatingIndex;
}
bool ChefDelegate::CheckCycleComplete()
{
return !mRunningTime.IsNull() && mRunningTime.Value() >= kCycleSeconds;
}
void ChefDelegate::EndCycle()
{
mRunningTime.SetNull();
CHIP_ERROR err = GetInstance()->SetCurrentPhase(DataModel::NullNullable);
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "EndCycle: Error setting current phase %" CHIP_ERROR_FORMAT, err.Format());
}
}
bool ChefDelegate::CheckCycleActive()
{
return !mRunningTime.IsNull();
}
uint8_t ChefDelegate::GetCurrentOperationalState()
{
return GetInstance()->GetCurrentOperationalState();
}
void ChefDelegate::HandleStartStateCallback(OperationalState::GenericOperationalError & err)
{
OperationalState::GenericOperationalError current_err(to_underlying(OperationalState::ErrorStateEnum::kNoError));
GetInstance()->GetCurrentOperationalError(current_err);
uint8_t opState = GetCurrentOperationalState();
if (current_err.errorStateID != to_underlying(OperationalState::ErrorStateEnum::kNoError) ||
opState == to_underlying(OperationalStateEnum::kError))
{
err.Set(to_underlying(ErrorStateEnum::kUnableToStartOrResume));
return;
}
if (opState == to_underlying(OperationalStateEnum::kRunning))
{
err.Set(to_underlying(ErrorStateEnum::kNoError));
return;
}
if (CheckCycleActive())
EndCycle();
CHIP_ERROR error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning));
if (error != CHIP_NO_ERROR)
{
err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation));
return;
}
if (!StartCycle())
{
err.Set(to_underlying(ErrorStateEnum::kUnableToStartOrResume));
return;
}
error = DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), onOvenCavityOperationalStateTimerTick, this);
if (error != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "HandleStartStateCallback: Failed to start timer. %" CHIP_ERROR_FORMAT, error.Format());
err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation));
return;
}
err.Set(to_underlying(ErrorStateEnum::kNoError));
}
void ChefDelegate::HandleStopStateCallback(OperationalState::GenericOperationalError & err)
{
uint32_t RunningTime = mRunningTime.ValueOr(0);
uint8_t opState = GetCurrentOperationalState();
if (CheckCycleActive())
{
DeviceLayer::SystemLayer().CancelTimer(onOvenCavityOperationalStateTimerTick, this);
EndCycle();
}
if (opState != to_underlying(OperationalStateEnum::kRunning))
{
ChipLogDetail(DeviceLayer, "HandleStopStateCallback: Cycle not started. Current state = %d. Returning.", opState);
err.Set(to_underlying(ErrorStateEnum::kNoError));
return;
}
auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kStopped));
if (error != CHIP_NO_ERROR)
{
err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation));
return;
}
OperationalState::GenericOperationalError current_err(to_underlying(OperationalState::ErrorStateEnum::kNoError));
GetInstance()->GetCurrentOperationalError(current_err);
Optional<DataModel::Nullable<uint32_t>> totalTime((DataModel::Nullable<uint32_t>(RunningTime)));
GetInstance()->OnOperationCompletionDetected(static_cast<uint8_t>(current_err.errorStateID), totalTime, NullOptionalType());
err.Set(to_underlying(ErrorStateEnum::kNoError));
}
void ChefDelegate::HandlePauseStateCallback(OperationalState::GenericOperationalError & err)
{
err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); // Not supported.
}
void ChefDelegate::HandleResumeStateCallback(OperationalState::GenericOperationalError & err)
{
err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); // Not supported.
}
CHIP_ERROR ChefDelegate::GetOperationalStateAtIndex(size_t index, OperationalState::GenericOperationalState & operationalState)
{
if (index >= mOperationalStateList.size())
{
return CHIP_ERROR_NOT_FOUND;
}
operationalState = OperationalState::GenericOperationalState(to_underlying(mOperationalStateList[index]));
return CHIP_NO_ERROR;
}
CHIP_ERROR ChefDelegate::GetOperationalPhaseAtIndex(size_t index, MutableCharSpan & operationalPhase)
{
if (index >= mOperationalPhaseList.size())
{
return CHIP_ERROR_NOT_FOUND;
}
return CopyCharSpanToMutableCharSpan(mOperationalPhaseList[index], operationalPhase);
}
/**
* Initializes OvenCavityOperationalState cluster for the app (all endpoints where cluster is enabled).
*/
void InitChefOvenCavityOperationalStateCluster()
{
const uint16_t endpointCount = emberAfEndpointCount();
for (uint16_t endpointIndex = 0; endpointIndex < endpointCount; endpointIndex++)
{
EndpointId endpointId = emberAfEndpointFromIndex(endpointIndex);
if (endpointId == kInvalidEndpointId)
{
continue;
}
// Check if endpoint has OvenCavityOperationalState cluster enabled
uint16_t epIndex =
emberAfGetClusterServerEndpointIndex(endpointId, Id, MATTER_DM_OPERATIONAL_STATE_OVEN_CLUSTER_SERVER_ENDPOINT_COUNT);
if (epIndex >= kOvenCavityOperationalStateTableSize)
continue;
gDelegateTable[epIndex] = std::make_unique<ChefDelegate>();
gInstanceTable[epIndex] = std::make_unique<Instance>(gDelegateTable[epIndex].get(), endpointId);
gInstanceTable[epIndex]->Init();
ChipLogProgress(DeviceLayer, "Endpoint %d OvenCavityOperationalState Initialized.", endpointId);
}
}
} // namespace OvenCavityOperationalState
} // namespace Clusters
} // namespace app
} // namespace chip
#endif // MATTER_DM_PLUGIN_OVEN_CAVITY_OPERATIONAL_STATE_SERVER