| /** |
| * |
| * 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. |
| */ |
| |
| /** |
| * |
| * Copyright (c) 2020 Silicon Labs |
| * |
| * 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. |
| */ |
| /***************************************************************************//** |
| * @file |
| * @brief Routines for the Level Control plugin, which implements the |
| * Level Control cluster. |
| ******************************************************************************* |
| ******************************************************************************/ |
| |
| // this file contains all the common includes for clusters in the util |
| #include "app/framework/include/af.h" |
| |
| // clusters specific header |
| #include "level-control.h" |
| |
| #ifdef EMBER_AF_PLUGIN_REPORTING |
| #include "app/framework/plugin/reporting/reporting.h" |
| #endif |
| |
| #ifdef EMBER_AF_PLUGIN_ZLL_LEVEL_CONTROL_SERVER |
| #include "app/framework/plugin/zll-level-control-server/zll-level-control-server.h" |
| #endif //EMBER_AF_PLUGIN_ZLL_LEVEL_CONTROL_SERVER |
| |
| #ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_START_UP_CURRENT_LEVEL_ATTRIBUTE |
| static bool areStartUpLevelControlServerAttributesTokenized(uint8_t endpoint); |
| #endif |
| |
| #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 |
| |
| #ifdef EMBER_AF_PLUGIN_ZLL_LEVEL_CONTROL_SERVER |
| #define MIN_LEVEL EMBER_AF_PLUGIN_ZLL_LEVEL_CONTROL_SERVER_MINIMUM_LEVEL |
| #define MAX_LEVEL EMBER_AF_PLUGIN_ZLL_LEVEL_CONTROL_SERVER_MAXIMUM_LEVEL |
| #else |
| #define MIN_LEVEL EMBER_AF_PLUGIN_LEVEL_CONTROL_MINIMUM_LEVEL |
| #define MAX_LEVEL EMBER_AF_PLUGIN_LEVEL_CONTROL_MAXIMUM_LEVEL |
| #endif |
| |
| #define INVALID_STORED_LEVEL 0xFFFF |
| |
| #define STARTUP_CURRENT_LEVEL_USE_DEVICE_MINIMUM 0x00 |
| #define STARTUP_CURRENT_LEVEL_USE_PREVIOUS_LEVEL 0xFF |
| |
| typedef struct { |
| uint8_t commandId; |
| uint8_t moveToLevel; |
| bool increasing; |
| bool useOnLevel; |
| uint8_t onLevel; |
| uint16_t storedLevel; |
| uint32_t eventDurationMs; |
| uint32_t transitionTimeMs; |
| uint32_t elapsedTimeMs; |
| } EmberAfLevelControlState; |
| |
| static EmberAfLevelControlState stateTable[EMBER_AF_LEVEL_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT]; |
| |
| static EmberAfLevelControlState *getState(uint8_t endpoint); |
| |
| static void moveToLevelHandler(uint8_t commandId, |
| uint8_t level, |
| uint16_t transitionTimeDs, |
| uint8_t optionMask, |
| uint8_t optionOverride, |
| uint16_t storedLevel); |
| static void moveHandler(uint8_t commandId, |
| uint8_t moveMode, |
| uint8_t rate, |
| uint8_t optionMask, |
| uint8_t optionOverride); |
| static void stepHandler(uint8_t commandId, |
| uint8_t stepMode, |
| uint8_t stepSize, |
| uint16_t transitionTimeDs, |
| uint8_t optionMask, |
| uint8_t optionOverride); |
| static void stopHandler(uint8_t commandId, |
| uint8_t optionMask, |
| uint8_t optionOverride); |
| |
| static void setOnOffValue(uint8_t endpoint, bool onOff); |
| static void writeRemainingTime(uint8_t endpoint, uint16_t remainingTimeMs); |
| static bool shouldExecuteIfOff(uint8_t endpoint, |
| uint8_t commandId, |
| uint8_t optionMask, |
| uint8_t optionOverride); |
| |
| #if defined(ZCL_USING_LEVEL_CONTROL_CLUSTER_OPTIONS_ATTRIBUTE) \ |
| && defined(EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP) |
| static void reallyUpdateCoupledColorTemp(uint8_t endpoint); |
| #define updateCoupledColorTemp(endpoint) reallyUpdateCoupledColorTemp(endpoint) |
| #else |
| #define updateCoupledColorTemp(endpoint) |
| #endif // LEVEL...OPTIONS_ATTRIBUTE && COLOR...SERVER_TEMP |
| |
| static void schedule(uint8_t endpoint, uint32_t delayMs) |
| { |
| emberAfScheduleServerTickExtended(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| delayMs, |
| EMBER_AF_LONG_POLL, |
| EMBER_AF_OK_TO_SLEEP); |
| } |
| |
| static void deactivate(uint8_t endpoint) |
| { |
| emberAfDeactivateServerTick(endpoint, ZCL_LEVEL_CONTROL_CLUSTER_ID); |
| } |
| |
| static EmberAfLevelControlState *getState(uint8_t endpoint) |
| { |
| uint8_t ep = emberAfFindClusterServerEndpointIndex(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID); |
| return (ep == 0xFF ? NULL : &stateTable[ep]); |
| } |
| |
| #if defined(ZCL_USING_LEVEL_CONTROL_CLUSTER_OPTIONS_ATTRIBUTE) \ |
| && defined(EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP) |
| static void reallyUpdateCoupledColorTemp(uint8_t endpoint) |
| { |
| uint8_t options; |
| EmberAfStatus status = emberAfReadServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_OPTIONS_ATTRIBUTE_ID, |
| &options, |
| sizeof(options)); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("Unable to read Options attribute: 0x%X", |
| status); |
| return; |
| } |
| |
| if (READBITS(options, EMBER_ZCL_LEVEL_CONTROL_OPTIONS_COUPLE_COLOR_TEMP_TO_LEVEL)) { |
| emberAfPluginLevelControlCoupledColorTempChangeCallback(endpoint); |
| } |
| } |
| #endif // LEVEL...OPTIONS_ATTRIBUTE && COLOR...SERVER_TEMP |
| |
| void emberAfLevelControlClusterServerTickCallback(uint8_t endpoint) |
| { |
| EmberAfLevelControlState *state = getState(endpoint); |
| EmberAfStatus status; |
| uint8_t currentLevel; |
| |
| if (state == NULL) { |
| return; |
| } |
| |
| state->elapsedTimeMs += state->eventDurationMs; |
| |
| #if !defined(ZCL_USING_LEVEL_CONTROL_CLUSTER_OPTIONS_ATTRIBUTE) \ |
| && defined(EMBER_AF_PLUGIN_ZLL_LEVEL_CONTROL_SERVER) |
| if (emberAfPluginZllLevelControlServerIgnoreMoveToLevelMoveStepStop(endpoint, |
| state->commandId)) { |
| return; |
| } |
| #endif |
| |
| // Read the attribute; print error message and return if it can't be read |
| status = emberAfReadServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| (uint8_t *)¤tLevel, |
| sizeof(currentLevel)); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: reading current level %x", status); |
| writeRemainingTime(endpoint, 0); |
| return; |
| } |
| |
| emberAfLevelControlClusterPrint("Event: move from %d", currentLevel); |
| |
| // adjust by the proper amount, either up or down |
| if (state->transitionTimeMs == 0) { |
| // Immediate, not over a time interval. |
| currentLevel = state->moveToLevel; |
| } else if (state->increasing) { |
| assert(currentLevel < MAX_LEVEL); |
| assert(currentLevel < state->moveToLevel); |
| currentLevel++; |
| } else { |
| assert(MIN_LEVEL < currentLevel); |
| assert(state->moveToLevel < currentLevel); |
| currentLevel--; |
| } |
| |
| emberAfLevelControlClusterPrint(" to %d ", currentLevel); |
| emberAfLevelControlClusterPrintln("(diff %c1)", |
| state->increasing ? '+' : '-'); |
| |
| status = emberAfWriteServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| (uint8_t *)¤tLevel, |
| ZCL_INT8U_ATTRIBUTE_TYPE); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: writing current level %x", status); |
| writeRemainingTime(endpoint, 0); |
| return; |
| } |
| |
| updateCoupledColorTemp(endpoint); |
| |
| // The level has changed, so the scene is no longer valid. |
| if (emberAfContainsServer(endpoint, ZCL_SCENES_CLUSTER_ID)) { |
| emberAfScenesClusterMakeInvalidCallback(endpoint); |
| } |
| |
| // Are we at the requested level? |
| if (currentLevel == state->moveToLevel) { |
| if (state->commandId == ZCL_MOVE_TO_LEVEL_WITH_ON_OFF_COMMAND_ID |
| || state->commandId == ZCL_MOVE_WITH_ON_OFF_COMMAND_ID |
| || state->commandId == ZCL_STEP_WITH_ON_OFF_COMMAND_ID) { |
| setOnOffValue(endpoint, (currentLevel != MIN_LEVEL)); |
| if (currentLevel == MIN_LEVEL && state->useOnLevel) { |
| status = emberAfWriteServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| (uint8_t *)&state->onLevel, |
| ZCL_INT8U_ATTRIBUTE_TYPE); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: writing current level %x", |
| status); |
| } else { |
| updateCoupledColorTemp(endpoint); |
| } |
| } |
| } else { |
| if (state->storedLevel != INVALID_STORED_LEVEL) { |
| uint8_t storedLevel8u = (uint8_t) state->storedLevel; |
| status = emberAfWriteServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| (uint8_t *)&storedLevel8u, |
| ZCL_INT8U_ATTRIBUTE_TYPE); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: writing current level %x", |
| status); |
| } else { |
| updateCoupledColorTemp(endpoint); |
| } |
| } |
| } |
| writeRemainingTime(endpoint, 0); |
| } else { |
| writeRemainingTime(endpoint, |
| state->transitionTimeMs - state->elapsedTimeMs); |
| schedule(endpoint, state->eventDurationMs); |
| } |
| } |
| |
| static void writeRemainingTime(uint8_t endpoint, uint16_t remainingTimeMs) |
| { |
| #ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_LEVEL_CONTROL_REMAINING_TIME_ATTRIBUTE |
| // 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 = (remainingTimeMs + 99) / 100; |
| EmberStatus status = emberAfWriteServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_LEVEL_CONTROL_REMAINING_TIME_ATTRIBUTE_ID, |
| (uint8_t *)&remainingTimeDs, |
| ZCL_INT16U_ATTRIBUTE_TYPE); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: writing remaining time %x", status); |
| } |
| #endif |
| } |
| |
| static void setOnOffValue(uint8_t endpoint, bool onOff) |
| { |
| if (emberAfContainsServer(endpoint, ZCL_ON_OFF_CLUSTER_ID)) { |
| emberAfLevelControlClusterPrintln("Setting on/off to %p due to level change", |
| onOff ? "ON" : "OFF"); |
| emberAfOnOffClusterSetValueCallback(endpoint, |
| (onOff ? ZCL_ON_COMMAND_ID : ZCL_OFF_COMMAND_ID), |
| true); |
| } |
| } |
| |
| static bool shouldExecuteIfOff(uint8_t endpoint, |
| uint8_t commandId, |
| uint8_t optionMask, |
| uint8_t optionOverride) |
| { |
| #ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_OPTIONS_ATTRIBUTE |
| // 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 > ZCL_STOP_COMMAND_ID) { |
| return true; |
| } |
| |
| if (!emberAfContainsServer(endpoint, ZCL_ON_OFF_CLUSTER_ID)) { |
| return true; |
| } |
| |
| uint8_t options; |
| EmberAfStatus status = emberAfReadServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_OPTIONS_ATTRIBUTE_ID, |
| &options, |
| sizeof(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. |
| options = 0x00; |
| } |
| |
| bool on; |
| status = emberAfReadServerAttribute(endpoint, |
| ZCL_ON_OFF_CLUSTER_ID, |
| ZCL_ON_OFF_ATTRIBUTE_ID, |
| (uint8_t *)&on, |
| sizeof(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 decission making ------- |
| // -----------more readable ---------- |
| // |
| if (optionMask == 0xFF && optionOverride == 0xFF) { |
| // 0xFF are the default values passed to the command handler when |
| // the payload is not present - in that case there is use of option |
| // attribute to decide execution of the command |
| return READBITS(options, EMBER_ZCL_LEVEL_CONTROL_OPTIONS_EXECUTE_IF_OFF); |
| } |
| // ---------- The above is to distinguish if the payload is present or not |
| |
| if (READBITS(optionMask, EMBER_ZCL_LEVEL_CONTROL_OPTIONS_EXECUTE_IF_OFF)) { |
| // Mask is present and set in the command payload, this indicates |
| // use the over ride as temporary option |
| return READBITS(optionOverride, EMBER_ZCL_LEVEL_CONTROL_OPTIONS_EXECUTE_IF_OFF); |
| } |
| // if we are here - use the option bits |
| return (READBITS(options, EMBER_ZCL_LEVEL_CONTROL_OPTIONS_EXECUTE_IF_OFF)); |
| |
| #else |
| // By default, we return true to continue supporting backwards compatibility. |
| return true; |
| #endif |
| } |
| |
| bool emberAfLevelControlClusterMoveToLevelCallback(uint8_t level, |
| uint16_t transitionTime, |
| uint8_t optionMask, |
| uint8_t optionOverride) |
| { |
| emberAfLevelControlClusterPrintln("%pMOVE_TO_LEVEL %x %2x %x %x", |
| "RX level-control:", |
| level, |
| transitionTime, |
| optionMask, |
| optionOverride); |
| moveToLevelHandler(ZCL_MOVE_TO_LEVEL_COMMAND_ID, |
| level, |
| transitionTime, |
| optionMask, |
| optionOverride, |
| INVALID_STORED_LEVEL); // Don't revert to the stored level |
| return true; |
| } |
| |
| bool emberAfLevelControlClusterMoveToLevelWithOnOffCallback(uint8_t level, |
| uint16_t transitionTime) |
| { |
| emberAfLevelControlClusterPrintln("%pMOVE_TO_LEVEL_WITH_ON_OFF %x %2x", |
| "RX level-control:", |
| level, |
| transitionTime); |
| moveToLevelHandler(ZCL_MOVE_TO_LEVEL_WITH_ON_OFF_COMMAND_ID, |
| level, |
| transitionTime, |
| 0xFF, |
| 0xFF, |
| INVALID_STORED_LEVEL); // Don't revert to the stored level |
| return true; |
| } |
| |
| bool emberAfLevelControlClusterMoveCallback(uint8_t moveMode, |
| uint8_t rate, |
| uint8_t optionMask, |
| uint8_t optionOverride) |
| { |
| emberAfLevelControlClusterPrintln("%pMOVE %x %x", |
| "RX level-control:", |
| moveMode, |
| rate); |
| moveHandler(ZCL_MOVE_COMMAND_ID, moveMode, rate, optionMask, optionOverride); |
| return true; |
| } |
| |
| bool emberAfLevelControlClusterMoveWithOnOffCallback(uint8_t moveMode, uint8_t rate) |
| { |
| emberAfLevelControlClusterPrintln("%pMOVE_WITH_ON_OFF %x %x", |
| "RX level-control:", |
| moveMode, |
| rate); |
| moveHandler(ZCL_MOVE_WITH_ON_OFF_COMMAND_ID, moveMode, rate, 0xFF, 0xFF); |
| return true; |
| } |
| |
| bool emberAfLevelControlClusterStepCallback(uint8_t stepMode, |
| uint8_t stepSize, |
| uint16_t transitionTime, |
| uint8_t optionMask, |
| uint8_t optionOverride) |
| { |
| emberAfLevelControlClusterPrintln("%pSTEP %x %x %2x", |
| "RX level-control:", |
| stepMode, |
| stepSize, |
| transitionTime); |
| stepHandler(ZCL_STEP_COMMAND_ID, stepMode, stepSize, transitionTime, optionMask, optionOverride); |
| return true; |
| } |
| |
| bool emberAfLevelControlClusterStepWithOnOffCallback(uint8_t stepMode, |
| uint8_t stepSize, |
| uint16_t transitionTime) |
| { |
| emberAfLevelControlClusterPrintln("%pSTEP_WITH_ON_OFF %x %x %2x", |
| "RX level-control:", |
| stepMode, |
| stepSize, |
| transitionTime); |
| stepHandler(ZCL_STEP_WITH_ON_OFF_COMMAND_ID, |
| stepMode, |
| stepSize, |
| transitionTime, |
| 0xFF, |
| 0xFF); |
| return true; |
| } |
| |
| bool emberAfLevelControlClusterStopCallback(uint8_t optionMask, |
| uint8_t optionOverride) |
| { |
| emberAfLevelControlClusterPrintln("%pSTOP", "RX level-control:"); |
| stopHandler(ZCL_STOP_COMMAND_ID, optionMask, optionOverride); |
| return true; |
| } |
| |
| bool emberAfLevelControlClusterStopWithOnOffCallback(void) |
| { |
| emberAfLevelControlClusterPrintln("%pSTOP_WITH_ON_OFF", "RX level-control:"); |
| stopHandler(ZCL_STOP_WITH_ON_OFF_COMMAND_ID, 0xFF, 0xFF); |
| return true; |
| } |
| |
| static void moveToLevelHandler(uint8_t commandId, |
| uint8_t level, |
| uint16_t transitionTimeDs, |
| uint8_t optionMask, |
| uint8_t optionOverride, |
| uint16_t storedLevel) |
| { |
| uint8_t endpoint = emberAfCurrentEndpoint(); |
| EmberAfLevelControlState *state = getState(endpoint); |
| EmberAfStatus status; |
| uint8_t currentLevel; |
| uint8_t actualStepSize; |
| |
| if (state == NULL) { |
| status = EMBER_ZCL_STATUS_FAILURE; |
| goto send_default_response; |
| } |
| |
| if (!shouldExecuteIfOff(endpoint, commandId, optionMask, optionOverride)) { |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| goto send_default_response; |
| } |
| |
| // Cancel any currently active command before fiddling with the state. |
| deactivate(endpoint); |
| |
| status = emberAfReadServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| (uint8_t *)¤tLevel, |
| sizeof(currentLevel)); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: reading current level %x", status); |
| goto send_default_response; |
| } |
| |
| 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 (MAX_LEVEL < level) { |
| state->moveToLevel = MAX_LEVEL; |
| } else if (level < MIN_LEVEL) { |
| state->moveToLevel = MIN_LEVEL; |
| } 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 <= state->moveToLevel) { |
| if (commandId == ZCL_MOVE_TO_LEVEL_WITH_ON_OFF_COMMAND_ID) { |
| setOnOffValue(endpoint, (state->moveToLevel != MIN_LEVEL)); |
| } |
| if (currentLevel == state->moveToLevel) { |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| goto send_default_response; |
| } |
| state->increasing = true; |
| actualStepSize = state->moveToLevel - currentLevel; |
| } else { |
| state->increasing = false; |
| actualStepSize = currentLevel - state->moveToLevel; |
| } |
| |
| // If the Transition time field takes the value 0xFFFF, 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 == 0xFFFF) { |
| #ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_ON_OFF_TRANSITION_TIME_ATTRIBUTE |
| status = emberAfReadServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_ON_OFF_TRANSITION_TIME_ATTRIBUTE_ID, |
| (uint8_t *)&transitionTimeDs, |
| sizeof(transitionTimeDs)); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: reading on/off transition time %x", |
| status); |
| goto send_default_response; |
| } |
| |
| // 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 |
| * MILLISECOND_TICKS_PER_SECOND |
| / 10); |
| #else //ZCL_USING_LEVEL_CONTROL_CLUSTER_ON_OFF_TRANSITION_TIME_ATTRIBUTE |
| // 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 //ZCL_USING_LEVEL_CONTROL_CLUSTER_ON_OFF_TRANSITION_TIME_ATTRIBUTE |
| } 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 |
| * MILLISECOND_TICKS_PER_SECOND |
| / 10); |
| } |
| |
| // The duration between events will be the transition time divided by the |
| // distance we must move. |
| state->eventDurationMs = state->transitionTimeMs / actualStepSize; |
| state->elapsedTimeMs = 0; |
| |
| // OnLevel is not used for Move commands. |
| state->useOnLevel = false; |
| |
| state->storedLevel = storedLevel; |
| |
| // The setup was successful, so mark the new state as active and return. |
| schedule(endpoint, state->eventDurationMs); |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| |
| #ifdef EMBER_AF_PLUGIN_ZLL_LEVEL_CONTROL_SERVER |
| if (commandId == ZCL_MOVE_TO_LEVEL_WITH_ON_OFF_COMMAND_ID) { |
| emberAfPluginZllLevelControlServerMoveToLevelWithOnOffZllExtensions(emberAfCurrentCommand()); |
| } |
| #endif |
| |
| send_default_response: |
| if (emberAfCurrentCommand()->apsFrame->clusterId |
| == ZCL_LEVEL_CONTROL_CLUSTER_ID) { |
| emberAfSendImmediateDefaultResponse(status); |
| } |
| } |
| |
| static void moveHandler(uint8_t commandId, uint8_t moveMode, uint8_t rate, uint8_t optionMask, uint8_t optionOverride) |
| { |
| uint8_t endpoint = emberAfCurrentEndpoint(); |
| EmberAfLevelControlState *state = getState(endpoint); |
| EmberAfStatus status; |
| uint8_t currentLevel; |
| uint8_t difference; |
| |
| if (state == NULL) { |
| status = EMBER_ZCL_STATUS_FAILURE; |
| goto send_default_response; |
| } |
| |
| if (!shouldExecuteIfOff(endpoint, commandId, optionMask, optionOverride)) { |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| goto send_default_response; |
| } |
| |
| // Cancel any currently active command before fiddling with the state. |
| deactivate(endpoint); |
| |
| status = emberAfReadServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| (uint8_t *)¤tLevel, |
| sizeof(currentLevel)); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: reading current level %x", status); |
| 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 = MAX_LEVEL; |
| difference = MAX_LEVEL - currentLevel; |
| break; |
| case EMBER_ZCL_MOVE_MODE_DOWN: |
| state->increasing = false; |
| state->moveToLevel = MIN_LEVEL; |
| difference = currentLevel - MIN_LEVEL; |
| break; |
| default: |
| status = EMBER_ZCL_STATUS_INVALID_FIELD; |
| 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 <= state->moveToLevel) { |
| if (commandId == ZCL_MOVE_WITH_ON_OFF_COMMAND_ID) { |
| setOnOffValue(endpoint, (state->moveToLevel != MIN_LEVEL)); |
| } |
| if (currentLevel == state->moveToLevel) { |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| goto send_default_response; |
| } |
| } |
| |
| // If the Rate field is 0xFF, the device should move as fast as it is able. |
| // Otherwise, the rate is in units per second. |
| if (rate == 0xFF) { |
| state->eventDurationMs = FASTEST_TRANSITION_TIME_MS; |
| } else { |
| state->eventDurationMs = MILLISECOND_TICKS_PER_SECOND / rate; |
| } |
| state->transitionTimeMs = difference * state->eventDurationMs; |
| state->elapsedTimeMs = 0; |
| |
| // OnLevel is not used for Move commands. |
| state->useOnLevel = false; |
| |
| // The setup was successful, so mark the new state as active and return. |
| schedule(endpoint, state->eventDurationMs); |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| |
| send_default_response: |
| emberAfSendImmediateDefaultResponse(status); |
| } |
| |
| static void stepHandler(uint8_t commandId, |
| uint8_t stepMode, |
| uint8_t stepSize, |
| uint16_t transitionTimeDs, |
| uint8_t optionMask, |
| uint8_t optionOverride) |
| { |
| uint8_t endpoint = emberAfCurrentEndpoint(); |
| EmberAfLevelControlState *state = getState(endpoint); |
| EmberAfStatus status; |
| uint8_t currentLevel; |
| uint8_t actualStepSize = stepSize; |
| |
| if (state == NULL) { |
| status = EMBER_ZCL_STATUS_FAILURE; |
| goto send_default_response; |
| } |
| |
| if (!shouldExecuteIfOff(endpoint, commandId, optionMask, optionOverride)) { |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| goto send_default_response; |
| } |
| |
| // Cancel any currently active command before fiddling with the state. |
| deactivate(endpoint); |
| |
| status = emberAfReadServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| (uint8_t *)¤tLevel, |
| sizeof(currentLevel)); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: reading current level %x", status); |
| 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 (MAX_LEVEL - currentLevel < stepSize) { |
| state->moveToLevel = MAX_LEVEL; |
| actualStepSize = (MAX_LEVEL - currentLevel); |
| } else { |
| state->moveToLevel = currentLevel + stepSize; |
| } |
| break; |
| case EMBER_ZCL_STEP_MODE_DOWN: |
| state->increasing = false; |
| if (currentLevel - MIN_LEVEL < stepSize) { |
| state->moveToLevel = MIN_LEVEL; |
| actualStepSize = (currentLevel - MIN_LEVEL); |
| } else { |
| state->moveToLevel = currentLevel - stepSize; |
| } |
| break; |
| default: |
| status = EMBER_ZCL_STATUS_INVALID_FIELD; |
| 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 <= state->moveToLevel) { |
| if (commandId == ZCL_STEP_WITH_ON_OFF_COMMAND_ID) { |
| setOnOffValue(endpoint, (state->moveToLevel != MIN_LEVEL)); |
| } |
| if (currentLevel == state->moveToLevel) { |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| goto send_default_response; |
| } |
| } |
| |
| // If the Transition Time field is 0xFFFF, the device should move as fast as |
| // it is able. |
| if (transitionTimeDs == 0xFFFF) { |
| state->transitionTimeMs = FASTEST_TRANSITION_TIME_MS; |
| } else { |
| // Transition time comes in as tenths of a second, but we work in |
| // milliseconds. |
| state->transitionTimeMs = (transitionTimeDs |
| * 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); |
| } |
| } |
| |
| // The duration between events will be the transition time divided by the |
| // distance we must move. |
| state->eventDurationMs = state->transitionTimeMs / actualStepSize; |
| state->elapsedTimeMs = 0; |
| |
| // OnLevel is not used for Step commands. |
| state->useOnLevel = false; |
| |
| // The setup was successful, so mark the new state as active and return. |
| schedule(endpoint, state->eventDurationMs); |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| |
| send_default_response: |
| emberAfSendImmediateDefaultResponse(status); |
| } |
| |
| static void stopHandler(uint8_t commandId, |
| uint8_t optionMask, |
| uint8_t optionOverride) |
| { |
| uint8_t endpoint = emberAfCurrentEndpoint(); |
| EmberAfLevelControlState *state = getState(endpoint); |
| EmberAfStatus status; |
| |
| if (state == NULL) { |
| status = EMBER_ZCL_STATUS_FAILURE; |
| goto send_default_response; |
| } |
| |
| if (!shouldExecuteIfOff(endpoint, commandId, optionMask, optionOverride)) { |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| goto send_default_response; |
| } |
| |
| // Cancel any currently active command. |
| deactivate(endpoint); |
| writeRemainingTime(endpoint, 0); |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| |
| send_default_response: |
| emberAfSendImmediateDefaultResponse(status); |
| } |
| |
| // Follows 07-5123-04 (ZigBee Cluster Library doc), section 3.10.2.1.1. |
| // Quotes are from table 3.46. |
| void emberAfOnOffClusterLevelControlEffectCallback(uint8_t endpoint, |
| bool newValue) |
| { |
| uint8_t temporaryCurrentLevelCache; |
| uint16_t currentOnOffTransitionTime; |
| uint8_t resolvedLevel; |
| uint8_t minimumLevelAllowedForTheDevice = MIN_LEVEL; |
| EmberAfStatus status; |
| |
| // "Temporarily store CurrentLevel." |
| status = emberAfReadServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| (uint8_t *)&temporaryCurrentLevelCache, |
| sizeof(temporaryCurrentLevelCache)); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: reading current level %x", status); |
| return; |
| } |
| |
| // Read the OnLevel attribute. |
| #ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_ON_LEVEL_ATTRIBUTE |
| status = emberAfReadServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_ON_LEVEL_ATTRIBUTE_ID, |
| (uint8_t *)&resolvedLevel, // OnLevel value |
| sizeof(resolvedLevel)); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: reading current level %x", status); |
| return; |
| } |
| |
| if (resolvedLevel == 0xFF) { |
| // OnLevel has undefined value; fall back to CurrentLevel. |
| resolvedLevel = temporaryCurrentLevelCache; |
| } |
| #else |
| resolvedLevel = temporaryCurrentLevelCache; |
| #endif |
| |
| // Read the OnOffTransitionTime attribute. |
| #ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_ON_OFF_TRANSITION_TIME_ATTRIBUTE |
| status = emberAfReadServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_ON_OFF_TRANSITION_TIME_ATTRIBUTE_ID, |
| (uint8_t *)¤tOnOffTransitionTime, |
| sizeof(currentOnOffTransitionTime)); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) { |
| emberAfLevelControlClusterPrintln("ERR: reading current level %x", status); |
| return; |
| } |
| #else |
| currentOnOffTransitionTime = 0xFFFF; |
| #endif |
| |
| if (newValue) { |
| // If newValue is ZCL_ON_COMMAND_ID... |
| // "Set CurrentLevel to minimum level allowed for the device." |
| status = emberAfWriteServerAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| (uint8_t *)&minimumLevelAllowedForTheDevice, |
| ZCL_INT8U_ATTRIBUTE_TYPE); |
| 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(ZCL_MOVE_TO_LEVEL_COMMAND_ID, |
| resolvedLevel, |
| currentOnOffTransitionTime, |
| 0xFF, |
| 0xFF, |
| INVALID_STORED_LEVEL); // Don't revert to stored level |
| } else { |
| // ...else if newValue is ZCL_OFF_COMMAND_ID... |
| // "Move CurrentLevel to the minimum level allowed for the device over the |
| // time period OnOffTransitionTime." |
| moveToLevelHandler(ZCL_MOVE_TO_LEVEL_COMMAND_ID, |
| minimumLevelAllowedForTheDevice, |
| currentOnOffTransitionTime, |
| 0xFF, |
| 0xFF, |
| temporaryCurrentLevelCache); |
| |
| // "If OnLevel is not defined, set the CurrentLevel to the stored level." |
| // The emberAfLevelControlClusterServerTickCallback implementation handles |
| // this. |
| } |
| } |
| |
| void emberAfLevelControlClusterServerInitCallback(uint8_t endpoint) |
| { |
| #ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_START_UP_CURRENT_LEVEL_ATTRIBUTE |
| // StartUp behavior relies StartUpCurrentLevel attributes being tokenized. |
| if (areStartUpLevelControlServerAttributesTokenized(endpoint)) { |
| // Read the StartUpOnOff attribute and set the OnOff attribute as per |
| // following from zcl 7 14-0127-20i-zcl-ch-3-general.doc. |
| // 3.10.2.2.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 3 58. 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. |
| // 0xff Set the CurrentLevel attribute to its previous value. |
| |
| // Initialize startUpCurrentLevel to assume previous value for currentLevel. |
| uint8_t startUpCurrentLevel = STARTUP_CURRENT_LEVEL_USE_PREVIOUS_LEVEL; |
| EmberAfStatus status = emberAfReadAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_START_UP_CURRENT_LEVEL_ATTRIBUTE_ID, |
| CLUSTER_MASK_SERVER, |
| (uint8_t *)&startUpCurrentLevel, |
| sizeof(startUpCurrentLevel), |
| NULL); |
| if (status == EMBER_ZCL_STATUS_SUCCESS) { |
| uint8_t currentLevel = 0; |
| status = emberAfReadAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| CLUSTER_MASK_SERVER, |
| (uint8_t *)¤tLevel, |
| sizeof(currentLevel), |
| NULL); |
| if (status == EMBER_ZCL_STATUS_SUCCESS) { |
| switch (startUpCurrentLevel) { |
| case STARTUP_CURRENT_LEVEL_USE_DEVICE_MINIMUM: |
| currentLevel = MIN_LEVEL; |
| break; |
| case STARTUP_CURRENT_LEVEL_USE_PREVIOUS_LEVEL: |
| // Just fetched it. |
| break; |
| default: |
| // Otherwise set to specified value 0x01-0xFE. |
| // But, need to enforce currentLevel's min/max, right? |
| // Spec doesn't mention this. |
| if (startUpCurrentLevel < MIN_LEVEL) { |
| currentLevel = MIN_LEVEL; |
| } else if (startUpCurrentLevel > MAX_LEVEL) { |
| currentLevel = MAX_LEVEL; |
| } else { |
| currentLevel = startUpCurrentLevel; |
| } |
| break; |
| } |
| status = emberAfWriteAttribute(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| CLUSTER_MASK_SERVER, |
| (uint8_t *)¤tLevel, |
| ZCL_INT8U_ATTRIBUTE_TYPE); |
| } |
| } |
| } |
| #endif |
| emberAfPluginLevelControlClusterServerPostInitCallback(endpoint); |
| } |
| |
| #ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_START_UP_CURRENT_LEVEL_ATTRIBUTE |
| static bool areStartUpLevelControlServerAttributesTokenized(uint8_t endpoint) |
| { |
| EmberAfAttributeMetadata *metadata; |
| |
| metadata = emberAfLocateAttributeMetadata(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, |
| CLUSTER_MASK_SERVER, |
| EMBER_AF_NULL_MANUFACTURER_CODE); |
| if (!emberAfAttributeIsTokenized(metadata)) { |
| return false; |
| } |
| |
| metadata = emberAfLocateAttributeMetadata(endpoint, |
| ZCL_LEVEL_CONTROL_CLUSTER_ID, |
| ZCL_START_UP_CURRENT_LEVEL_ATTRIBUTE_ID, |
| CLUSTER_MASK_SERVER, |
| EMBER_AF_NULL_MANUFACTURER_CODE); |
| if (!emberAfAttributeIsTokenized(metadata)) { |
| return false; |
| } |
| |
| return true; |
| } |
| #endif |