| /** |
| * |
| * 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, ¤tOnOffTransitionTime); |
| 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() {} |