blob: 77242ea554f39d527d055c14a5e3bc5a7656b5f8 [file] [log] [blame]
/**
*
* Copyright (c) 2020 Project CHIP Authors
*
* 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.
*/
// clusters specific header
#include "level-control.h"
// this file contains all the common includes for clusters in the util
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <app/util/af.h>
#include <app/util/config.h>
#include <app/util/error-mapping.h>
#include <app/util/util.h>
#include <app/reporting/reporting.h>
#include <lib/core/Optional.h>
#include <platform/CHIPDeviceConfig.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/PlatformManager.h>
#ifdef EMBER_AF_PLUGIN_SCENES
#include <app/clusters/scenes/scenes.h>
#endif // EMBER_AF_PLUGIN_SCENES
#ifdef EMBER_AF_PLUGIN_ON_OFF
#include <app/clusters/on-off-server/on-off-server.h>
#endif // EMBER_AF_PLUGIN_ON_OFF
#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP
#include <app/clusters/color-control-server/color-control-server.h>
#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP
#include <assert.h>
using namespace chip;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::LevelControl;
using chip::Protocols::InteractionModel::Status;
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_START_UP_CURRENT_LEVEL
static bool areStartUpLevelControlServerAttributesNonVolatile(EndpointId endpoint);
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_START_UP_CURRENT_LEVEL
#if (EMBER_AF_PLUGIN_LEVEL_CONTROL_RATE == 0)
#define FASTEST_TRANSITION_TIME_MS 0
#else
#define FASTEST_TRANSITION_TIME_MS (MILLISECOND_TICKS_PER_SECOND / EMBER_AF_PLUGIN_LEVEL_CONTROL_RATE)
#endif // EMBER_AF_PLUGIN_LEVEL_CONTROL_RATE
#define LEVEL_CONTROL_LIGHTING_MIN_LEVEL 0x01
#define LEVEL_CONTROL_LIGHTING_MAX_LEVEL 0xFE
#define INVALID_STORED_LEVEL 0xFFFF
#define STARTUP_CURRENT_LEVEL_USE_DEVICE_MINIMUM 0x00
#define STARTUP_CURRENT_LEVEL_USE_PREVIOUS_LEVEL 0xFF
static constexpr size_t kLevelControlStateTableSize =
EMBER_AF_LEVEL_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT;
struct CallbackScheduleState
{
System::Clock::Timestamp idealTimestamp; // The ideal time-stamp for the next callback to be scheduled.
System::Clock::Milliseconds32 runTime; // The duration of the previous scheduled callback function.
// e.g. running time of emberAfLevelControlClusterServerTickCallback
// when called consecutively
};
typedef struct
{
CommandId commandId;
uint8_t moveToLevel;
bool increasing;
uint8_t onLevel;
uint8_t minLevel;
uint8_t maxLevel;
uint16_t storedLevel;
uint32_t eventDurationMs;
uint32_t transitionTimeMs;
uint32_t elapsedTimeMs;
CallbackScheduleState callbackSchedule;
} EmberAfLevelControlState;
static EmberAfLevelControlState stateTable[kLevelControlStateTableSize];
static EmberAfLevelControlState * getState(EndpointId endpoint);
static Status moveToLevelHandler(EndpointId endpoint, CommandId commandId, uint8_t level,
app::DataModel::Nullable<uint16_t> transitionTimeDs,
chip::Optional<BitMask<LevelControlOptions>> optionsMask,
chip::Optional<BitMask<LevelControlOptions>> optionsOverride, uint16_t storedLevel);
static void moveHandler(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, uint8_t moveMode,
app::DataModel::Nullable<uint8_t> rate, chip::Optional<BitMask<LevelControlOptions>> optionsMask,
chip::Optional<BitMask<LevelControlOptions>> optionsOverride);
static void stepHandler(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, uint8_t stepMode,
uint8_t stepSize, app::DataModel::Nullable<uint16_t> transitionTimeDs,
chip::Optional<BitMask<LevelControlOptions>> optionsMask,
chip::Optional<BitMask<LevelControlOptions>> optionsOverride);
static void stopHandler(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
chip::Optional<BitMask<LevelControlOptions>> optionsMask,
chip::Optional<BitMask<LevelControlOptions>> optionsOverride);
static void setOnOffValue(EndpointId endpoint, bool onOff);
static void writeRemainingTime(EndpointId endpoint, uint16_t remainingTimeMs);
static bool shouldExecuteIfOff(EndpointId endpoint, CommandId commandId,
chip::Optional<chip::BitMask<LevelControlOptions>> optionsMask,
chip::Optional<chip::BitMask<LevelControlOptions>> optionsOverride);
#if !defined(IGNORE_LEVEL_CONTROL_CLUSTER_OPTIONS) && defined(EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP)
static void reallyUpdateCoupledColorTemp(EndpointId endpoint);
#define updateCoupledColorTemp(endpoint) reallyUpdateCoupledColorTemp(endpoint)
#else
#define updateCoupledColorTemp(endpoint)
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_OPTIONS && EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP
void emberAfLevelControlClusterServerTickCallback(EndpointId endpoint);
static void timerCallback(System::Layer *, void * callbackContext)
{
emberAfLevelControlClusterServerTickCallback(static_cast<EndpointId>(reinterpret_cast<uintptr_t>(callbackContext)));
}
static uint32_t computeCallbackWaitTimeMs(CallbackScheduleState & callbackSchedule, uint32_t delayMs)
{
auto delay = System::Clock::Milliseconds32(delayMs);
auto waitTime = delay;
const auto currentTime = System::SystemClock().GetMonotonicTimestamp();
// Subsequent call
if (callbackSchedule.runTime.count())
{
// Check whether the previous scheduled callback was late and whether its running time
// is smaller than the desired delay
// If the running time of the scheduled callback is greater than the desired delay
// then do nothing; do not flood the event loop if the device is not fast enough
if ((currentTime > callbackSchedule.idealTimestamp) && (callbackSchedule.runTime < delay))
{
System::Clock::Timestamp latency = currentTime - callbackSchedule.idealTimestamp;
if (latency >= delay)
{
waitTime = System::Clock::Milliseconds32(0);
}
else
{
waitTime -= latency;
}
}
}
// First-time call
else
{
// initialize idealTimestamp
callbackSchedule.idealTimestamp = currentTime;
}
callbackSchedule.idealTimestamp += System::Clock::Milliseconds32(delayMs);
callbackSchedule.runTime = System::Clock::Milliseconds32(0);
return waitTime.count();
}
static void scheduleTimerCallbackMs(EndpointId endpoint, uint32_t delayMs)
{
CHIP_ERROR err = DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(delayMs), timerCallback,
reinterpret_cast<void *>(static_cast<uintptr_t>(endpoint)));
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Level Control Server failed to schedule event: %" CHIP_ERROR_FORMAT, err.Format());
}
}
static void cancelEndpointTimerCallback(EndpointId endpoint)
{
DeviceLayer::SystemLayer().CancelTimer(timerCallback, reinterpret_cast<void *>(static_cast<uintptr_t>(endpoint)));
}
static EmberAfLevelControlState * getState(EndpointId endpoint)
{
uint16_t ep = emberAfFindClusterServerEndpointIndex(endpoint, LevelControl::Id);
return (ep == 0xFFFF ? nullptr : &stateTable[ep]);
}
#if !defined(IGNORE_LEVEL_CONTROL_CLUSTER_OPTIONS) && defined(EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP)
static void reallyUpdateCoupledColorTemp(EndpointId endpoint)
{
LevelControl::Attributes::Options::TypeInfo::Type options;
EmberAfStatus status = Attributes::Options::Get(endpoint, &options);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("Unable to read Options attribute: 0x%X", status);
return;
}
if (emberAfContainsAttribute(endpoint, ColorControl::Id, ColorControl::Attributes::ColorTemperatureMireds::Id))
{
if (options.Has(LevelControlOptions::kCoupleColorTempToLevel))
{
emberAfPluginLevelControlCoupledColorTempChangeCallback(endpoint);
}
}
}
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_OPTIONS && EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP
void emberAfLevelControlClusterServerTickCallback(EndpointId endpoint)
{
EmberAfLevelControlState * state = getState(endpoint);
EmberAfStatus status;
app::DataModel::Nullable<uint8_t> currentLevel;
const auto callbackStartTimestamp = System::SystemClock().GetMonotonicTimestamp();
if (state == nullptr)
{
return;
}
state->elapsedTimeMs += state->eventDurationMs;
// Read the attribute; print error message and return if it can't be read
status = LevelControl::Attributes::CurrentLevel::Get(endpoint, currentLevel);
if (status != EMBER_ZCL_STATUS_SUCCESS || currentLevel.IsNull())
{
emberAfLevelControlClusterPrintln("ERR: reading current level %x", status);
state->callbackSchedule.runTime = System::Clock::Milliseconds32(0);
writeRemainingTime(endpoint, 0);
return;
}
emberAfLevelControlClusterPrint("Event: move from %d", currentLevel.Value());
// adjust by the proper amount, either up or down
if (state->transitionTimeMs == 0)
{
// Immediate, not over a time interval.
currentLevel.SetNonNull(state->moveToLevel);
}
else if (state->increasing)
{
assert(currentLevel.Value() < state->maxLevel);
assert(currentLevel.Value() < state->moveToLevel);
currentLevel.SetNonNull(static_cast<uint8_t>(currentLevel.Value() + 1));
}
else
{
assert(state->minLevel < currentLevel.Value());
assert(state->moveToLevel < currentLevel.Value());
currentLevel.SetNonNull(static_cast<uint8_t>(currentLevel.Value() - 1));
}
emberAfLevelControlClusterPrint(" to %d ", currentLevel.Value());
emberAfLevelControlClusterPrintln("(diff %c1)", state->increasing ? '+' : '-');
status = Attributes::CurrentLevel::Set(endpoint, currentLevel);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("ERR: writing current level %x", status);
state->callbackSchedule.runTime = System::Clock::Milliseconds32(0);
writeRemainingTime(endpoint, 0);
return;
}
updateCoupledColorTemp(endpoint);
#ifdef EMBER_AF_PLUGIN_SCENES
// The level has changed, so the scene is no longer valid.
if (emberAfContainsServer(endpoint, Scenes::Id))
{
emberAfScenesClusterMakeInvalidCallback(endpoint);
}
#endif // EMBER_AF_PLUGIN_SCENES
// Are we at the requested level?
if (currentLevel.Value() == state->moveToLevel)
{
if (state->commandId == Commands::MoveToLevelWithOnOff::Id || state->commandId == Commands::MoveWithOnOff::Id ||
state->commandId == Commands::StepWithOnOff::Id)
{
setOnOffValue(endpoint, (currentLevel.Value() != state->minLevel));
}
if (state->storedLevel != INVALID_STORED_LEVEL)
{
uint8_t storedLevel8u = (uint8_t) state->storedLevel;
status = Attributes::CurrentLevel::Set(endpoint, storedLevel8u);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("ERR: writing current level %x", status);
}
else
{
updateCoupledColorTemp(endpoint);
}
}
state->callbackSchedule.runTime = System::Clock::Milliseconds32(0);
writeRemainingTime(endpoint, 0);
}
else
{
state->callbackSchedule.runTime = System::SystemClock().GetMonotonicTimestamp() - callbackStartTimestamp;
writeRemainingTime(endpoint, static_cast<uint16_t>(state->transitionTimeMs - state->elapsedTimeMs));
scheduleTimerCallbackMs(endpoint, computeCallbackWaitTimeMs(state->callbackSchedule, state->eventDurationMs));
}
}
static void writeRemainingTime(EndpointId endpoint, uint16_t remainingTimeMs)
{
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_LEVEL_CONTROL_REMAINING_TIME
if (emberAfContainsAttribute(endpoint, LevelControl::Id, LevelControl::Attributes::RemainingTime::Id))
{
// Convert milliseconds to tenths of a second, rounding any fractional value
// up to the nearest whole value. This means:
//
// 0 ms = 0.00 ds = 0 ds
// 1 ms = 0.01 ds = 1 ds
// ...
// 100 ms = 1.00 ds = 1 ds
// 101 ms = 1.01 ds = 2 ds
// ...
// 200 ms = 2.00 ds = 2 ds
// 201 ms = 2.01 ds = 3 ds
// ...
//
// This is done to ensure that the attribute, in tenths of a second, only
// goes to zero when the remaining time in milliseconds is actually zero.
uint16_t remainingTimeDs = static_cast<uint16_t>((remainingTimeMs + 99) / 100);
EmberStatus status = LevelControl::Attributes::RemainingTime::Set(endpoint, remainingTimeDs);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("ERR: writing remaining time %x", status);
}
}
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_LEVEL_CONTROL_REMAINING_TIME
}
static void setOnOffValue(EndpointId endpoint, bool onOff)
{
#ifdef EMBER_AF_PLUGIN_ON_OFF
if (emberAfContainsServer(endpoint, OnOff::Id))
{
emberAfLevelControlClusterPrintln("Setting on/off to %s due to level change", onOff ? "ON" : "OFF");
OnOffServer::Instance().setOnOffValue(endpoint, (onOff ? OnOff::Commands::On::Id : OnOff::Commands::Off::Id), true);
}
#endif // EMBER_AF_PLUGIN_ON_OFF
}
static bool shouldExecuteIfOff(EndpointId endpoint, CommandId commandId,
chip::Optional<chip::BitMask<LevelControlOptions>> optionsMask,
chip::Optional<chip::BitMask<LevelControlOptions>> optionsOverride)
{
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_OPTIONS
if (emberAfContainsAttribute(endpoint, LevelControl::Id, Attributes::Options::Id))
{
// From 3.10.2.2.8.1 of ZCL7 document 14-0127-20j-zcl-ch-3-general.docx:
// "Command execution SHALL NOT continue beyond the Options processing if
// all of these criteria are true:
// - The command is one of the ‘without On/Off’ commands: Move, Move to
// Level, Stop, or Step.
// - The On/Off cluster exists on the same endpoint as this cluster.
// - The OnOff attribute of the On/Off cluster, on this endpoint, is 0x00
// (FALSE).
// - The value of the ExecuteIfOff bit is 0."
if (commandId > Commands::Stop::Id)
{
return true;
}
if (!emberAfContainsServer(endpoint, OnOff::Id))
{
return true;
}
LevelControl::Attributes::Options::TypeInfo::Type options;
EmberAfStatus status = Attributes::Options::Get(endpoint, &options);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("Unable to read Options attribute: 0x%X", status);
// If we can't read the attribute, then we should just assume that it has its
// default value.
}
bool on;
status = OnOff::Attributes::OnOff::Get(endpoint, &on);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("Unable to read OnOff attribute: 0x%X", status);
return true;
}
// The device is on - hence ExecuteIfOff does not matter
if (on)
{
return true;
}
// The OptionsMask & OptionsOverride fields SHALL both be present or both
// omitted in the command. A temporary Options bitmap SHALL be created from
// the Options attribute, using the OptionsMask & OptionsOverride fields, if
// present. Each bit of the temporary Options bitmap SHALL be determined as
// follows:
// Each bit in the Options attribute SHALL determine the corresponding bit in
// the temporary Options bitmap, unless the OptionsMask field is present and
// has the corresponding bit set to 1, in which case the corresponding bit in
// the OptionsOverride field SHALL determine the corresponding bit in the
// temporary Options bitmap.
// The resulting temporary Options bitmap SHALL then be processed as defined
// in section 3.10.2.2.3.
// ---------- The following order is important in decision making -------
// -----------more readable ----------
//
if (!optionsMask.HasValue() || !optionsOverride.HasValue())
{
// in case optionMask or optionOverride is not set, use of option
// attribute to decide execution of the command
return options.Has(LevelControlOptions::kExecuteIfOff);
}
// ---------- The above is to distinguish if the payload is present or not
if (optionsMask.Value().Has(LevelControlOptions::kExecuteIfOff))
{
// Mask is present and set in the command payload, this indicates
// use the over ride as temporary option
return optionsOverride.Value().Has(LevelControlOptions::kExecuteIfOff);
}
// if we are here - use the option bits
return options.Has(LevelControlOptions::kExecuteIfOff);
}
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_OPTIONS
// By default, we return true to continue supporting backwards compatibility.
return true;
}
bool emberAfLevelControlClusterMoveToLevelCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::MoveToLevel::DecodableType & commandData)
{
commandObj->AddStatus(commandPath, LevelControlServer::MoveToLevel(commandPath.mEndpointId, commandData));
return true;
}
namespace LevelControlServer {
Status MoveToLevel(EndpointId endpointId, const Commands::MoveToLevel::DecodableType & commandData)
{
auto & level = commandData.level;
auto & transitionTime = commandData.transitionTime;
auto & optionsMask = commandData.optionsMask;
auto & optionsOverride = commandData.optionsOverride;
if (transitionTime.IsNull())
{
emberAfLevelControlClusterPrintln("%s MOVE_TO_LEVEL %x null %x %x", "RX level-control:", level, optionsMask.Raw(),
optionsOverride.Raw());
}
else
{
emberAfLevelControlClusterPrintln("%s MOVE_TO_LEVEL %x %2x %x %x", "RX level-control:", level, transitionTime.Value(),
optionsMask.Raw(), optionsOverride.Raw());
}
return moveToLevelHandler(endpointId, Commands::MoveToLevel::Id, level, transitionTime,
Optional<BitMask<LevelControlOptions>>(optionsMask),
Optional<BitMask<LevelControlOptions>>(optionsOverride),
INVALID_STORED_LEVEL); // Don't revert to the stored level
}
} // namespace LevelControlServer
bool emberAfLevelControlClusterMoveToLevelWithOnOffCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::MoveToLevelWithOnOff::DecodableType & commandData)
{
auto & level = commandData.level;
auto & transitionTime = commandData.transitionTime;
auto & optionsMask = commandData.optionsMask;
auto & optionsOverride = commandData.optionsOverride;
if (transitionTime.IsNull())
{
emberAfLevelControlClusterPrintln("%s MOVE_TO_LEVEL_WITH_ON_OFF %x null %x %x", "RX level-control:", level,
optionsMask.Raw(), optionsOverride.Raw());
}
else
{
emberAfLevelControlClusterPrintln("%s MOVE_TO_LEVEL_WITH_ON_OFF %x %2x %x %x", "RX level-control:", level,
transitionTime.Value(), optionsMask.Raw(), optionsOverride.Raw());
}
Status status = moveToLevelHandler(commandPath.mEndpointId, Commands::MoveToLevelWithOnOff::Id, level, transitionTime,
Optional<BitMask<LevelControlOptions>>(optionsMask),
Optional<BitMask<LevelControlOptions>>(optionsOverride),
INVALID_STORED_LEVEL); // Don't revert to the stored level
commandObj->AddStatus(commandPath, status);
return true;
}
bool emberAfLevelControlClusterMoveCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::Move::DecodableType & commandData)
{
auto & moveMode = commandData.moveMode;
auto & rate = commandData.rate;
auto & optionsMask = commandData.optionsMask;
auto & optionsOverride = commandData.optionsOverride;
if (rate.IsNull())
{
emberAfLevelControlClusterPrintln("%s MOVE %x null %x %x", "RX level-control:", moveMode, optionsMask.Raw(),
optionsOverride.Raw());
}
else
{
emberAfLevelControlClusterPrintln("%s MOVE %x %u %x %x", "RX level-control:", moveMode, rate.Value(), optionsMask.Raw(),
optionsOverride.Raw());
}
moveHandler(commandObj, commandPath, moveMode, rate, Optional<BitMask<LevelControlOptions>>(optionsMask),
Optional<BitMask<LevelControlOptions>>(optionsOverride));
return true;
}
bool emberAfLevelControlClusterMoveWithOnOffCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::MoveWithOnOff::DecodableType & commandData)
{
auto & moveMode = commandData.moveMode;
auto & rate = commandData.rate;
auto & optionsMask = commandData.optionsMask;
auto & optionsOverride = commandData.optionsOverride;
if (rate.IsNull())
{
emberAfLevelControlClusterPrintln("%s MOVE_WITH_ON_OFF %x null %x %x", "RX level-control:", moveMode, optionsMask.Raw(),
optionsOverride.Raw());
}
else
{
emberAfLevelControlClusterPrintln("%s MOVE_WITH_ON_OFF %u %2x %x %x", "RX level-control:", moveMode, rate.Value(),
optionsMask.Raw(), optionsOverride.Raw());
}
moveHandler(commandObj, commandPath, moveMode, rate, Optional<BitMask<LevelControlOptions>>(optionsMask),
Optional<BitMask<LevelControlOptions>>(optionsOverride));
return true;
}
bool emberAfLevelControlClusterStepCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::Step::DecodableType & commandData)
{
auto & stepMode = commandData.stepMode;
auto & stepSize = commandData.stepSize;
auto & transitionTime = commandData.transitionTime;
auto & optionsMask = commandData.optionsMask;
auto & optionsOverride = commandData.optionsOverride;
if (transitionTime.IsNull())
{
emberAfLevelControlClusterPrintln("%s STEP %x %x null %x %x", "RX level-control:", stepMode, stepSize, optionsMask.Raw(),
optionsOverride.Raw());
}
else
{
emberAfLevelControlClusterPrintln("%s STEP %x %x %2x %x %x", "RX level-control:", stepMode, stepSize,
transitionTime.Value(), optionsMask.Raw(), optionsOverride.Raw());
}
stepHandler(commandObj, commandPath, stepMode, stepSize, transitionTime, Optional<BitMask<LevelControlOptions>>(optionsMask),
Optional<BitMask<LevelControlOptions>>(optionsOverride));
return true;
}
bool emberAfLevelControlClusterStepWithOnOffCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::StepWithOnOff::DecodableType & commandData)
{
auto & stepMode = commandData.stepMode;
auto & stepSize = commandData.stepSize;
auto & transitionTime = commandData.transitionTime;
auto & optionsMask = commandData.optionsMask;
auto & optionsOverride = commandData.optionsOverride;
if (transitionTime.IsNull())
{
emberAfLevelControlClusterPrintln("%s STEP_WITH_ON_OFF %x %x null %x %x", "RX level-control:", stepMode, stepSize,
optionsMask.Raw(), optionsOverride.Raw());
}
else
{
emberAfLevelControlClusterPrintln("%s STEP_WITH_ON_OFF %x %x %2x %x %x", "RX level-control:", stepMode, stepSize,
transitionTime.Value(), optionsMask.Raw(), optionsOverride.Raw());
}
stepHandler(commandObj, commandPath, stepMode, stepSize, transitionTime, Optional<BitMask<LevelControlOptions>>(optionsMask),
Optional<BitMask<LevelControlOptions>>(optionsOverride));
return true;
}
bool emberAfLevelControlClusterStopCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::Stop::DecodableType & commandData)
{
auto & optionsMask = commandData.optionsMask;
auto & optionsOverride = commandData.optionsOverride;
emberAfLevelControlClusterPrintln("%s STOP", "RX level-control:");
stopHandler(commandObj, commandPath, Optional<BitMask<LevelControlOptions>>(optionsMask),
Optional<BitMask<LevelControlOptions>>(optionsOverride));
return true;
}
bool emberAfLevelControlClusterStopWithOnOffCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::StopWithOnOff::DecodableType & commandData)
{
auto & optionsMask = commandData.optionsMask;
auto & optionsOverride = commandData.optionsOverride;
emberAfLevelControlClusterPrintln("%s STOP_WITH_ON_OFF", "RX level-control:");
stopHandler(commandObj, commandPath, Optional<BitMask<LevelControlOptions>>(optionsMask),
Optional<BitMask<LevelControlOptions>>(optionsOverride));
return true;
}
static Status moveToLevelHandler(EndpointId endpoint, CommandId commandId, uint8_t level,
app::DataModel::Nullable<uint16_t> transitionTimeDs,
chip::Optional<BitMask<LevelControlOptions>> optionsMask,
chip::Optional<BitMask<LevelControlOptions>> optionsOverride, uint16_t storedLevel)
{
EmberAfLevelControlState * state = getState(endpoint);
app::DataModel::Nullable<uint8_t> currentLevel;
uint8_t actualStepSize;
if (state == nullptr)
{
return Status::Failure;
}
if (level > EMBER_AF_PLUGIN_LEVEL_CONTROL_MAXIMUM_LEVEL)
{
return Status::InvalidCommand;
}
if (!shouldExecuteIfOff(endpoint, commandId, optionsMask, optionsOverride))
{
return Status::Success;
}
// Cancel any currently active command before fiddling with the state.
cancelEndpointTimerCallback(endpoint);
EmberAfStatus status = Attributes::CurrentLevel::Get(endpoint, currentLevel);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("ERR: reading current level %x", status);
return app::ToInteractionModelStatus(status);
}
if (currentLevel.IsNull())
{
emberAfLevelControlClusterPrintln("ERR: Current Level is null");
return Status::Failure;
}
state->commandId = commandId;
// Move To Level commands cause the device to move from its current level to
// the specified level at the specified rate.
if (state->maxLevel <= level)
{
state->moveToLevel = state->maxLevel;
}
else if (level <= state->minLevel)
{
state->moveToLevel = state->minLevel;
}
else
{
state->moveToLevel = level;
}
// If the level is decreasing, the On/Off attribute is left unchanged. This
// logic is to prevent a light from transitioning from off to bright to dim.
// Instead, a light that is off will stay off until the target level is
// reached.
if (currentLevel.Value() <= state->moveToLevel)
{
if (commandId == Commands::MoveToLevelWithOnOff::Id)
{
setOnOffValue(endpoint, (state->moveToLevel != state->minLevel));
}
if (currentLevel.Value() == state->moveToLevel)
{
return Status::Success;
}
state->increasing = true;
actualStepSize = static_cast<uint8_t>(state->moveToLevel - currentLevel.Value());
}
else
{
state->increasing = false;
actualStepSize = static_cast<uint8_t>(currentLevel.Value() - state->moveToLevel);
}
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_TRANSITION
// If the Transition time field takes the value null, then the time taken
// to move to the new level shall instead be determined by the On/Off
// Transition Time attribute. If On/Off Transition Time, which is an
// optional attribute, is not present, the device shall move to its new level
// as fast as it is able.
if (transitionTimeDs.IsNull())
{
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_ON_OFF_TRANSITION_TIME
if (emberAfContainsAttribute(endpoint, LevelControl::Id, Attributes::OnOffTransitionTime::Id))
{
uint16_t onOffTransitionTime = 0;
status = Attributes::OnOffTransitionTime::Get(endpoint, &onOffTransitionTime);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("ERR: reading on/off transition time %x", status);
return app::ToInteractionModelStatus(status);
}
// Transition time comes in (or is stored, in the case of On/Off Transition
// Time) as tenths of a second, but we work in milliseconds.
state->transitionTimeMs = (onOffTransitionTime * MILLISECOND_TICKS_PER_SECOND / 10);
}
else
{
state->transitionTimeMs = FASTEST_TRANSITION_TIME_MS;
}
#else
// If the Transition Time field is 0xFFFF and On/Off Transition Time,
// which is an optional attribute, is not present, the device shall move to
// its new level as fast as it is able.
state->transitionTimeMs = FASTEST_TRANSITION_TIME_MS;
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_ON_OFF_TRANSITION_TIME
}
else
{
// Transition time comes in (or is stored, in the case of On/Off Transition
// Time) as tenths of a second, but we work in milliseconds.
state->transitionTimeMs = (transitionTimeDs.Value() * MILLISECOND_TICKS_PER_SECOND / 10);
}
#else
// Transition is not supported so always use fastest transition time and ignore
// both the provided transition time as well as OnOffTransitionTime.
emberAfLevelControlClusterPrintln("Device does not support transition, ignoring transition time");
state->transitionTimeMs = FASTEST_TRANSITION_TIME_MS;
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_TRANSITION
// The duration between events will be the transition time divided by the
// distance we must move.
state->eventDurationMs = state->transitionTimeMs / actualStepSize;
state->elapsedTimeMs = 0;
state->storedLevel = storedLevel;
state->callbackSchedule.runTime = System::Clock::Milliseconds32(0);
// The setup was successful, so mark the new state as active and return.
scheduleTimerCallbackMs(endpoint, computeCallbackWaitTimeMs(state->callbackSchedule, state->eventDurationMs));
#ifdef EMBER_AF_PLUGIN_ON_OFF
// Check that the received MoveToLevelWithOnOff produces a On action and that the onoff support the lighting featuremap
if (commandId == Commands::MoveToLevelWithOnOff::Id && state->moveToLevel != state->minLevel &&
OnOffServer::Instance().SupportsLightingApplications(endpoint))
{
OnOff::Attributes::GlobalSceneControl::Set(endpoint, true);
}
#endif // EMBER_AF_PLUGIN_ON_OFF
return Status::Success;
}
static void moveHandler(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, uint8_t moveMode,
app::DataModel::Nullable<uint8_t> rate, chip::Optional<BitMask<LevelControlOptions>> optionsMask,
chip::Optional<BitMask<LevelControlOptions>> optionsOverride)
{
EndpointId endpoint = commandPath.mEndpointId;
CommandId commandId = commandPath.mCommandId;
EmberAfLevelControlState * state = getState(endpoint);
Status status;
app::DataModel::Nullable<uint8_t> currentLevel;
uint8_t difference;
if (state == nullptr)
{
status = Status::Failure;
goto send_default_response;
}
if (!shouldExecuteIfOff(endpoint, commandId, optionsMask, optionsOverride))
{
status = Status::Success;
goto send_default_response;
}
// Cancel any currently active command before fiddling with the state.
cancelEndpointTimerCallback(endpoint);
status = app::ToInteractionModelStatus(Attributes::CurrentLevel::Get(endpoint, currentLevel));
if (status != Status::Success)
{
emberAfLevelControlClusterPrintln("ERR: reading current level %x", to_underlying(status));
goto send_default_response;
}
if (currentLevel.IsNull())
{
emberAfLevelControlClusterPrintln("ERR: Current Level is null");
status = Status::Failure;
goto send_default_response;
}
state->commandId = commandId;
// Move commands cause the device to move from its current level to either
// the maximum or minimum level at the specified rate.
switch (moveMode)
{
case EMBER_ZCL_MOVE_MODE_UP:
state->increasing = true;
state->moveToLevel = state->maxLevel;
difference = static_cast<uint8_t>(state->maxLevel - currentLevel.Value());
break;
case EMBER_ZCL_MOVE_MODE_DOWN:
state->increasing = false;
state->moveToLevel = state->minLevel;
difference = static_cast<uint8_t>(currentLevel.Value() - state->minLevel);
break;
default:
status = Status::InvalidCommand;
goto send_default_response;
}
// If the level is decreasing, the On/Off attribute is left unchanged. This
// logic is to prevent a light from transitioning from off to bright to dim.
// Instead, a light that is off will stay off until the target level is
// reached.
if (currentLevel.Value() <= state->moveToLevel)
{
if (commandId == Commands::MoveWithOnOff::Id)
{
setOnOffValue(endpoint, (state->moveToLevel != state->minLevel));
}
if (currentLevel.Value() == state->moveToLevel)
{
status = Status::Success;
goto send_default_response;
}
}
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_TRANSITION
// If the Rate field is null, the device should move at the default move rate, if available,
// Otherwise, move as fast as possible
if (rate.IsNull())
{
app::DataModel::Nullable<uint8_t> defaultMoveRate;
status = app::ToInteractionModelStatus(Attributes::DefaultMoveRate::Get(endpoint, defaultMoveRate));
if (status != Status::Success || defaultMoveRate.IsNull())
{
emberAfLevelControlClusterPrintln("ERR: reading default move rate %x", to_underlying(status));
state->eventDurationMs = FASTEST_TRANSITION_TIME_MS;
}
else
{
// nonsensical case, means "don't move", so we're done
if (defaultMoveRate.Value() == 0)
{
status = Status::Success;
goto send_default_response;
}
state->eventDurationMs = MILLISECOND_TICKS_PER_SECOND / defaultMoveRate.Value();
}
}
else
{
state->eventDurationMs = MILLISECOND_TICKS_PER_SECOND / rate.Value();
}
#else
// Transition/rate is not supported so always use fastest transition time and ignore
// both the provided transition time as well as OnOffTransitionTime.
emberAfLevelControlClusterPrintln("Device does not support transition, ignoring rate");
state->eventDurationMs = FASTEST_TRANSITION_TIME_MS;
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_TRANSITION
state->transitionTimeMs = difference * state->eventDurationMs;
state->elapsedTimeMs = 0;
// storedLevel is not used for Move commands.
state->storedLevel = INVALID_STORED_LEVEL;
state->callbackSchedule.runTime = System::Clock::Milliseconds32(0);
// The setup was successful, so mark the new state as active and return.
scheduleTimerCallbackMs(endpoint, computeCallbackWaitTimeMs(state->callbackSchedule, state->eventDurationMs));
status = Status::Success;
send_default_response:
commandObj->AddStatus(commandPath, status);
}
static void stepHandler(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, uint8_t stepMode,
uint8_t stepSize, app::DataModel::Nullable<uint16_t> transitionTimeDs,
chip::Optional<BitMask<LevelControlOptions>> optionsMask,
chip::Optional<BitMask<LevelControlOptions>> optionsOverride)
{
EndpointId endpoint = commandPath.mEndpointId;
CommandId commandId = commandPath.mCommandId;
EmberAfLevelControlState * state = getState(endpoint);
Status status;
app::DataModel::Nullable<uint8_t> currentLevel;
uint8_t actualStepSize = stepSize;
if (state == nullptr)
{
status = Status::Failure;
goto send_default_response;
}
if (!shouldExecuteIfOff(endpoint, commandId, optionsMask, optionsOverride))
{
status = Status::Success;
goto send_default_response;
}
// Cancel any currently active command before fiddling with the state.
cancelEndpointTimerCallback(endpoint);
status = app::ToInteractionModelStatus(Attributes::CurrentLevel::Get(endpoint, currentLevel));
if (status != Status::Success)
{
emberAfLevelControlClusterPrintln("ERR: reading current level %x", to_underlying(status));
goto send_default_response;
}
if (currentLevel.IsNull())
{
emberAfLevelControlClusterPrintln("ERR: Current Level is null");
status = Status::Failure;
goto send_default_response;
}
state->commandId = commandId;
// Step commands cause the device to move from its current level to a new
// level over the specified transition time.
switch (stepMode)
{
case EMBER_ZCL_STEP_MODE_UP:
state->increasing = true;
if (state->maxLevel - currentLevel.Value() < stepSize)
{
state->moveToLevel = state->maxLevel;
actualStepSize = static_cast<uint8_t>(state->maxLevel - currentLevel.Value());
}
else
{
state->moveToLevel = static_cast<uint8_t>(currentLevel.Value() + stepSize);
}
break;
case EMBER_ZCL_STEP_MODE_DOWN:
state->increasing = false;
if (currentLevel.Value() - state->minLevel < stepSize)
{
state->moveToLevel = state->minLevel;
actualStepSize = static_cast<uint8_t>(currentLevel.Value() - state->minLevel);
}
else
{
state->moveToLevel = static_cast<uint8_t>(currentLevel.Value() - stepSize);
}
break;
default:
status = Status::InvalidCommand;
goto send_default_response;
}
// If the level is decreasing, the On/Off attribute is left unchanged. This
// logic is to prevent a light from transitioning from off to bright to dim.
// Instead, a light that is off will stay off until the target level is
// reached.
if (currentLevel.Value() <= state->moveToLevel)
{
if (commandId == Commands::StepWithOnOff::Id)
{
setOnOffValue(endpoint, (state->moveToLevel != state->minLevel));
}
if (currentLevel.Value() == state->moveToLevel)
{
status = Status::Success;
goto send_default_response;
}
}
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_TRANSITION
// If the Transition Time field is null, the device should move as fast as
// it is able.
if (transitionTimeDs.IsNull())
{
state->transitionTimeMs = FASTEST_TRANSITION_TIME_MS;
}
else
{
// Transition time comes in as tenths of a second, but we work in
// milliseconds.
state->transitionTimeMs = (transitionTimeDs.Value() * MILLISECOND_TICKS_PER_SECOND / 10);
// If the new level was pegged at the minimum level, the transition time
// shall be proportionally reduced. This is done after the conversion to
// milliseconds to reduce rounding errors in integer division.
if (stepSize != actualStepSize)
{
state->transitionTimeMs = (state->transitionTimeMs * actualStepSize / stepSize);
}
}
#else
// Transition is not supported so always use fastest transition time and ignore
// both the provided transition time as well as OnOffTransitionTime.
emberAfLevelControlClusterPrintln("Device does not support transition, ignoring transition time");
state->transitionTimeMs = FASTEST_TRANSITION_TIME_MS;
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_TRANSITION
// The duration between events will be the transition time divided by the
// distance we must move.
state->eventDurationMs = state->transitionTimeMs / actualStepSize;
state->elapsedTimeMs = 0;
// storedLevel is not used for Step commands
state->storedLevel = INVALID_STORED_LEVEL;
state->callbackSchedule.runTime = System::Clock::Milliseconds32(0);
// The setup was successful, so mark the new state as active and return.
scheduleTimerCallbackMs(endpoint, computeCallbackWaitTimeMs(state->callbackSchedule, state->eventDurationMs));
status = Status::Success;
send_default_response:
commandObj->AddStatus(commandPath, status);
}
static void stopHandler(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
chip::Optional<BitMask<LevelControlOptions>> optionsMask,
chip::Optional<BitMask<LevelControlOptions>> optionsOverride)
{
EndpointId endpoint = commandPath.mEndpointId;
CommandId commandId = commandPath.mCommandId;
EmberAfLevelControlState * state = getState(endpoint);
Status status;
if (state == nullptr)
{
status = Status::Failure;
goto send_default_response;
}
if (!shouldExecuteIfOff(endpoint, commandId, optionsMask, optionsOverride))
{
status = Status::Success;
goto send_default_response;
}
// Cancel any currently active command.
cancelEndpointTimerCallback(endpoint);
writeRemainingTime(endpoint, 0);
status = Status::Success;
send_default_response:
commandObj->AddStatus(commandPath, status);
}
// Follows 07-5123-04 (ZigBee Cluster Library doc), section 3.10.2.1.1.
// Quotes are from table 3.46.
void emberAfOnOffClusterLevelControlEffectCallback(EndpointId endpoint, bool newValue)
{
app::DataModel::Nullable<uint8_t> resolvedLevel;
app::DataModel::Nullable<uint8_t> temporaryCurrentLevelCache;
app::DataModel::Nullable<uint16_t> transitionTime;
uint16_t currentOnOffTransitionTime;
EmberAfStatus status;
bool useOnLevel = false;
EmberAfLevelControlState * state = getState(endpoint);
if (state == nullptr)
{
emberAfLevelControlClusterPrintln("ERR: Level control cluster not available on ep%d", endpoint);
return;
}
uint8_t minimumLevelAllowedForTheDevice = state->minLevel;
// "Temporarily store CurrentLevel."
status = Attributes::CurrentLevel::Get(endpoint, temporaryCurrentLevelCache);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("ERR: reading current level %x", status);
return;
}
if (temporaryCurrentLevelCache.IsNull())
{
emberAfLevelControlClusterPrintln("ERR: Current Level is null");
return;
}
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_ON_LEVEL_ATTRIBUTE
if (emberAfContainsAttribute(endpoint, LevelControl::Id, Attributes::OnLevel::Id))
{
status = Attributes::OnLevel::Get(endpoint, resolvedLevel);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("ERR: reading on level %x", status);
return;
}
if (resolvedLevel.IsNull())
{
// OnLevel has undefined value; fall back to CurrentLevel.
resolvedLevel.SetNonNull(temporaryCurrentLevelCache.Value());
}
else
{
useOnLevel = true;
}
}
else
{
resolvedLevel.SetNonNull(temporaryCurrentLevelCache.Value());
}
#else
resolvedLevel.SetNonNull(temporaryCurrentLevelCache.Value());
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_ON_LEVEL_ATTRIBUTE
// Read the OnOffTransitionTime attribute.
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_ON_OFF_TRANSITION_TIME
if (emberAfContainsAttribute(endpoint, LevelControl::Id, Attributes::OnOffTransitionTime::Id))
{
status = Attributes::OnOffTransitionTime::Get(endpoint, &currentOnOffTransitionTime);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("ERR: reading current level %x", status);
return;
}
transitionTime.SetNonNull(currentOnOffTransitionTime);
}
else
{
transitionTime.SetNull();
}
#else
transitionTime.SetNull();
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_ON_OFF_TRANSITION_TIME
if (newValue)
{
// If newValue is OnOff::Commands::On::Id...
// "Set CurrentLevel to minimum level allowed for the device."
status = Attributes::CurrentLevel::Set(endpoint, minimumLevelAllowedForTheDevice);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfLevelControlClusterPrintln("ERR: reading current level %x", status);
return;
}
// "Move CurrentLevel to OnLevel, or to the stored level if OnLevel is not
// defined, over the time period OnOffTransitionTime."
moveToLevelHandler(endpoint, Commands::MoveToLevel::Id, resolvedLevel.Value(), transitionTime, chip::NullOptional,
chip::NullOptional,
INVALID_STORED_LEVEL); // Don't revert to stored level
}
else
{
// ...else if newValue is OnOff::Commands::Off::Id...
// "Move CurrentLevel to the minimum level allowed for the device over the
// time period OnOffTransitionTime."
if (useOnLevel)
{
// If OnLevel is defined, don't revert to stored level.
moveToLevelHandler(endpoint, Commands::MoveToLevelWithOnOff::Id, minimumLevelAllowedForTheDevice, transitionTime,
chip::NullOptional, chip::NullOptional, INVALID_STORED_LEVEL);
}
else
{
// If OnLevel is not defined, set the CurrentLevel to the stored level.
moveToLevelHandler(endpoint, Commands::MoveToLevelWithOnOff::Id, minimumLevelAllowedForTheDevice, transitionTime,
chip::NullOptional, chip::NullOptional, temporaryCurrentLevelCache.Value());
}
}
}
void emberAfLevelControlClusterServerInitCallback(EndpointId endpoint)
{
EmberAfLevelControlState * state = getState(endpoint);
if (state == nullptr)
{
emberAfLevelControlClusterPrintln("ERR: Level control cluster not available on ep%d", endpoint);
return;
}
state->minLevel = EMBER_AF_PLUGIN_LEVEL_CONTROL_MINIMUM_LEVEL;
state->maxLevel = EMBER_AF_PLUGIN_LEVEL_CONTROL_MAXIMUM_LEVEL;
// If these read only attributes are enabled we use those values as our set minLevel and maxLevel
// if get isn't possible, value stays at default
Attributes::MinLevel::Get(endpoint, &state->minLevel);
Attributes::MaxLevel::Get(endpoint, &state->maxLevel);
if (LevelControlHasFeature(endpoint, LevelControlFeature::kLighting))
{
if (state->minLevel < LEVEL_CONTROL_LIGHTING_MIN_LEVEL)
{
state->minLevel = LEVEL_CONTROL_LIGHTING_MIN_LEVEL;
}
if (state->maxLevel > LEVEL_CONTROL_LIGHTING_MAX_LEVEL)
{
state->maxLevel = LEVEL_CONTROL_LIGHTING_MAX_LEVEL;
}
}
app::DataModel::Nullable<uint8_t> currentLevel;
EmberAfStatus status = Attributes::CurrentLevel::Get(endpoint, currentLevel);
if (status == EMBER_ZCL_STATUS_SUCCESS)
{
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_START_UP_CURRENT_LEVEL
// StartUp behavior relies StartUpCurrentLevel attributes being Non Volatile.
if (areStartUpLevelControlServerAttributesNonVolatile(endpoint))
{
// 1.5.14. StartUpCurrentLevel Attribute
// The StartUpCurrentLevel attribute SHALL define the desired startup level
// for a device when it is supplied with power and this level SHALL be
// reflected in the CurrentLevel attribute. The values of the StartUpCurrentLevel
// attribute are listed below:
// Table 4. Values of the StartUpCurrentLevel attribute
// Value Action on power up
// 0x00 Set the CurrentLevel attribute to the minimum value permitted on the device.
// 0x01-0xfe Set the CurrentLevel attribute to this value.
// NULL Set the CurrentLevel attribute to its previous value.
// 0xFF Work Around ZAP Can't set default value to NULL
// https://github.com/project-chip/zap/issues/354
app::DataModel::Nullable<uint8_t> startUpCurrentLevel;
status = Attributes::StartUpCurrentLevel::Get(endpoint, startUpCurrentLevel);
if (status == EMBER_ZCL_STATUS_SUCCESS)
{
if (!startUpCurrentLevel.IsNull())
{
if (startUpCurrentLevel.Value() == STARTUP_CURRENT_LEVEL_USE_DEVICE_MINIMUM)
{
currentLevel.SetNonNull(state->minLevel);
}
else
{
// Otherwise set to specified value 0x01-0xFE.
// But, need to enforce currentLevel's min/max, right?
// Spec doesn't mention this.
if (startUpCurrentLevel.Value() < state->minLevel)
{
currentLevel.SetNonNull(state->minLevel);
}
else if (startUpCurrentLevel.Value() > state->maxLevel)
{
currentLevel.SetNonNull(state->maxLevel);
}
else
{
currentLevel.SetNonNull(startUpCurrentLevel.Value());
}
}
}
// Otherwise Set the CurrentLevel attribute to its previous value which was already fetch above
Attributes::CurrentLevel::Set(endpoint, currentLevel);
}
}
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_START_UP_CURRENT_LEVEL
// In any case, we make sure that the respects min/max
if (currentLevel.IsNull() || currentLevel.Value() < state->minLevel)
{
Attributes::CurrentLevel::Set(endpoint, state->minLevel);
}
else if (currentLevel.Value() > state->maxLevel)
{
Attributes::CurrentLevel::Set(endpoint, state->maxLevel);
}
}
emberAfPluginLevelControlClusterServerPostInitCallback(endpoint);
}
void MatterLevelControlClusterServerShutdownCallback(EndpointId endpoint)
{
emberAfLevelControlClusterPrintln("Shuting down level control server cluster on endpoint %d", endpoint);
cancelEndpointTimerCallback(endpoint);
}
#ifndef IGNORE_LEVEL_CONTROL_CLUSTER_START_UP_CURRENT_LEVEL
static bool areStartUpLevelControlServerAttributesNonVolatile(EndpointId endpoint)
{
return !emberAfIsKnownVolatileAttribute(endpoint, LevelControl::Id, Attributes::CurrentLevel::Id) &&
!emberAfIsKnownVolatileAttribute(endpoint, LevelControl::Id, Attributes::StartUpCurrentLevel::Id);
}
#endif // IGNORE_LEVEL_CONTROL_CLUSTER_START_UP_CURRENT_LEVEL
void emberAfPluginLevelControlClusterServerPostInitCallback(EndpointId endpoint) {}
bool LevelControlHasFeature(EndpointId endpoint, LevelControlFeature feature)
{
bool success;
uint32_t featureMap;
success = (Attributes::FeatureMap::Get(endpoint, &featureMap) == EMBER_ZCL_STATUS_SUCCESS);
return success ? ((featureMap & to_underlying(feature)) != 0) : false;
}
void MatterLevelControlPluginServerInitCallback() {}