diff --git a/src/app/clusters/color-control-server/color-control-server.cpp b/src/app/clusters/color-control-server/color-control-server.cpp index 6d9a0bc270dd71..effc95f74f29e2 100644 --- a/src/app/clusters/color-control-server/color-control-server.cpp +++ b/src/app/clusters/color-control-server/color-control-server.cpp @@ -17,10 +17,10 @@ /** * - * Copyright (c) 2020 Silicon Labs + * Copyright (c) 2020-21 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 not use this file except in compliance the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 @@ -31,12 +31,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/**************************************************************************** - * @file - * @brief Routines for the Color Control Server - *plugin. - ******************************************************************************* - ******************************************************************************/ + #include "color-control-server.h" #include @@ -47,797 +42,702 @@ #include #include #include -#include #include #include using namespace chip; -using namespace app::Clusters::ColorControl; - -#define COLOR_TEMP_CONTROL emberAfPluginColorControlServerTempTransitionEventControl -#define COLOR_XY_CONTROL emberAfPluginColorControlServerXyTransitionEventControl -#define COLOR_HSV_CONTROL emberAfPluginColorControlServerHueSatTransitionEventControl - -// move mode -enum -{ - MOVE_MODE_STOP = 0x00, - MOVE_MODE_UP = 0x01, - MOVE_MODE_DOWN = 0x03 -}; - -enum -{ - COLOR_MODE_HSV = 0x00, - COLOR_MODE_CIE_XY = 0x01, - COLOR_MODE_TEMPERATURE = 0x02 -}; - -enum -{ - HSV_TO_HSV = 0x00, - HSV_TO_CIE_XY = 0x01, - HSV_TO_TEMPERATURE = 0x02, - CIE_XY_TO_HSV = 0x10, - CIE_XY_TO_CIE_XY = 0x11, - CIE_XY_TO_TEMPERATURE = 0x12, - TEMPERATURE_TO_HSV = 0x20, - TEMPERATURE_TO_CIE_XY = 0x21, - TEMPERATURE_TO_TEMPERATURE = 0x22 -}; - -EmberEventControl emberAfPluginColorControlServerTempTransitionEventControl; -EmberEventControl emberAfPluginColorControlServerXyTransitionEventControl; -EmberEventControl emberAfPluginColorControlServerHueSatTransitionEventControl; - -#define UPDATE_TIME_MS 100 -#define TRANSITION_TIME_1S 10 -#define MIN_CIE_XY_VALUE 0 -// this value comes directly from the ZCL specification table 5.3 -#define MAX_CIE_XY_VALUE 0xfeff -#define MIN_TEMPERATURE_VALUE 0 -#define MAX_TEMPERATURE_VALUE 0xfeff -#define MIN_HUE_VALUE 0 -#define MAX_HUE_VALUE 254 -#define MIN_SATURATION_VALUE 0 -#define MAX_SATURATION_VALUE 254 -#define HALF_MAX_UINT8T 127 -#define HALF_MAX_UINT16T 0x7FFF - -#define MAX_ENHANCED_HUE_VALUE 0xFFFF - -#define MIN_CURRENT_LEVEL 0x01 -#define MAX_CURRENT_LEVEL 0xFE - -#define REPORT_FAILED 0xFF - -typedef struct -{ - uint8_t initialHue; - uint8_t currentHue; - uint8_t finalHue; - uint16_t stepsRemaining; - uint16_t stepsTotal; - uint16_t initialEnhancedHue; - uint16_t currentEnhancedHue; - uint16_t finalEnhancedHue; - EndpointId endpoint; - bool up; - bool repeat; - bool isEnhancedHue; -} ColorHueTransitionState; - -static ColorHueTransitionState colorHueTransitionState; - -typedef struct -{ - uint16_t initialValue; - uint16_t currentValue; - uint16_t finalValue; - uint16_t stepsRemaining; - uint16_t stepsTotal; - uint16_t lowLimit; - uint16_t highLimit; - EndpointId endpoint; -} Color16uTransitionState; - -static Color16uTransitionState colorXTransitionState; -static Color16uTransitionState colorYTransitionState; - -static Color16uTransitionState colorTempTransitionState; - -static Color16uTransitionState colorSaturationTransitionState; - -// Forward declarations: -static bool computeNewColor16uValue(Color16uTransitionState * p); -static void stopAllColorTransitions(void); -static void handleModeSwitch(EndpointId endpoint, uint8_t newColorMode); -static bool shouldExecuteIfOff(EndpointId endpoint, uint8_t optionMask, uint8_t optionOverride); +using namespace app::Clusters; -#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV -static uint8_t addHue(uint8_t hue1, uint8_t hue2); -static uint8_t subtractHue(uint8_t hue1, uint8_t hue2); -static uint8_t addSaturation(uint8_t saturation1, uint8_t saturation2); -static uint8_t subtractSaturation(uint8_t saturation1, uint8_t saturation2); -static void initHueSat(EndpointId endpoint); -static uint8_t readHue(EndpointId endpoint); -static uint8_t readSaturation(EndpointId endpoint); -static uint16_t addEnhancedHue(uint16_t hue1, uint16_t hue2); -static uint16_t subtractEnhancedHue(uint16_t hue1, uint16_t hue2); -static uint16_t readEnhancedHue(EndpointId endpoint); - -static bool moveHue(uint8_t moveMode, uint16_t rate, uint8_t optionsMask, uint8_t optionsOverride, bool isEnhanced); -static bool moveToHue(uint16_t hue, uint8_t hueMoveMode, uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride, - bool isEnhanced); -static bool moveToHueAndSaturation(uint16_t hue, uint8_t saturation, uint16_t transitionTime, uint8_t optionsMask, - uint8_t optionsOverride, bool isEnhanced); -static bool stepHue(uint8_t stepMode, uint16_t stepSize, uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride, - bool isEnhanced); -#endif +/********************************************************** + * Attributes Definition + *********************************************************/ -#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY -static uint16_t findNewColorValueFromStep(uint16_t oldValue, int16_t step); -static uint16_t readColorX(EndpointId endpoint); -static uint16_t readColorY(EndpointId endpoint); -#endif +ColorControlServer ColorControlServer::instance; -static uint16_t computeTransitionTimeFromStateAndRate(Color16uTransitionState * p, uint16_t rate); +/********************************************************** + * ColorControl Implementation + *********************************************************/ -// convenient token handling functions -static uint8_t readColorMode(EndpointId endpoint) +ColorControlServer & ColorControlServer::Instance() { - uint8_t colorMode; - emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_COLOR_MODE_ATTRIBUTE_ID, - reinterpret_cast(&colorMode), sizeof(uint8_t)); - return colorMode; + return instance; } -static uint16_t readColorTemperature(EndpointId endpoint) +void ColorControlServer::stopAllColorTransitions(EndpointId endpoint) { - uint16_t colorTemperature; - emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_COLOR_TEMPERATURE_ATTRIBUTE_ID, - reinterpret_cast(&colorTemperature), sizeof(uint16_t)); - - return colorTemperature; + emberEventControlSetInactive(getEventControl(endpoint)); } -static uint16_t readColorTemperatureMin(EndpointId endpoint) +bool ColorControlServer::stopMoveStepCommand(uint8_t optionsMask, uint8_t optionsOverride) { - uint16_t colorTemperatureMin; - EmberAfStatus status; - - status = - emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_COLOR_TEMP_PHYSICAL_MIN_ATTRIBUTE_ID, - reinterpret_cast(&colorTemperatureMin), sizeof(uint16_t)); + EndpointId endpoint = emberAfCurrentEndpoint(); - if (status != EMBER_ZCL_STATUS_SUCCESS) + if (shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { - colorTemperatureMin = MIN_TEMPERATURE_VALUE; + stopAllColorTransitions(endpoint); } - return colorTemperatureMin; + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } -static uint16_t readColorTemperatureMax(EndpointId endpoint) +bool ColorControlServer::shouldExecuteIfOff(EndpointId endpoint, uint8_t optionMask, uint8_t optionOverride) { - uint16_t colorTemperatureMax; - EmberAfStatus status; - - status = - emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_COLOR_TEMP_PHYSICAL_MAX_ATTRIBUTE_ID, - reinterpret_cast(&colorTemperatureMax), sizeof(uint16_t)); + // From 5.2.2.2.1.10 of ZCL7 document 14-0129-15f-zcl-ch-5-lighting.docx: + // "Command execution SHALL NOT continue beyond the Options processing if + // all of these criteria are true: + // - 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 (status != EMBER_ZCL_STATUS_SUCCESS) + if (!emberAfContainsServer(endpoint, ZCL_ON_OFF_CLUSTER_ID)) { - colorTemperatureMax = MAX_TEMPERATURE_VALUE; + return true; } - return colorTemperatureMax; -} - -static uint16_t readColorTemperatureCoupleToLevelMin(EndpointId endpoint) -{ - uint16_t colorTemperatureCoupleToLevelMin; - EmberAfStatus status; + uint8_t options = 0x00; + ColorControl::Attributes::GetColorControlOptions(endpoint, &options); - status = emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, - ZCL_COLOR_CONTROL_TEMPERATURE_LEVEL_MIN_MIREDS_ATTRIBUTE_ID, - reinterpret_cast(&colorTemperatureCoupleToLevelMin), sizeof(uint16_t)); + bool on = true; + OnOff::Attributes::GetOnOff(endpoint, &on); - if (status != EMBER_ZCL_STATUS_SUCCESS) + // The device is on - hence ExecuteIfOff does not matter + if (on) { - // Not less than the physical min. - colorTemperatureCoupleToLevelMin = readColorTemperatureMin(endpoint); + 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 5.2.2.2.1.10. - return colorTemperatureCoupleToLevelMin; -} - -static uint8_t readLevelControlCurrentLevel(EndpointId endpoint) -{ - uint8_t currentLevel; - EmberAfStatus status; - - status = emberAfReadServerAttribute(endpoint, ZCL_LEVEL_CONTROL_CLUSTER_ID, ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, - reinterpret_cast(¤tLevel), sizeof(uint8_t)); - - if (status != EMBER_ZCL_STATUS_SUCCESS) + // ---------- The following order is important in decision making ------- + // -----------more readable ---------- + // + if (optionMask == 0xFF && optionOverride == 0xFF) { - currentLevel = 0x7F; // midpoint of range 0x01-0xFE + // 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_COLOR_CONTROL_OPTIONS_EXECUTE_IF_OFF); } + // ---------- The above is to distinguish if the payload is present or not - return currentLevel; -} - -static void writeRemainingTime(EndpointId endpoint, uint16_t remainingTime) -{ - emberAfWriteServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_REMAINING_TIME_ATTRIBUTE_ID, - reinterpret_cast(&remainingTime), ZCL_INT16U_ATTRIBUTE_TYPE); -} - -static void writeColorMode(EndpointId endpoint, uint8_t colorMode) -{ - emberAfWriteServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_ENHANCED_COLOR_MODE_ATTRIBUTE_ID, - reinterpret_cast(&colorMode), ZCL_INT8U_ATTRIBUTE_TYPE); - - emberAfWriteServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_COLOR_MODE_ATTRIBUTE_ID, - reinterpret_cast(&colorMode), ZCL_INT8U_ATTRIBUTE_TYPE); + if (READBITS(optionMask, EMBER_ZCL_COLOR_CONTROL_OPTIONS_EXECUTE_IF_OFF)) + { + // Mask is present and set in the command payload, this indicates + // use the override as temporary option + return READBITS(optionOverride, EMBER_ZCL_COLOR_CONTROL_OPTIONS_EXECUTE_IF_OFF); + } + // if we are here - use the option attribute bits + return (READBITS(options, EMBER_ZCL_COLOR_CONTROL_OPTIONS_EXECUTE_IF_OFF)); } -static void writeHue(EndpointId endpoint, uint8_t hue) +/** + * @brief The specification says that if we are transitioning from one color mode + * into another, we need to compute the new mode's attribute values from the + * old mode. However, it also says that if the old mode doesn't translate into + * the new mode, this must be avoided. + * I am putting in this function to compute the new attributes based on the old + * color mode. + * + * @param endpoint + * @param newColorMode + */ +void ColorControlServer::handleModeSwitch(EndpointId endpoint, uint8_t newColorMode) { - emberAfWriteServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_CURRENT_HUE_ATTRIBUTE_ID, - reinterpret_cast(&hue), ZCL_INT8U_ATTRIBUTE_TYPE); -} + uint8_t oldColorMode = 0; + ColorControl::Attributes::GetColorMode(endpoint, &oldColorMode); -static void writeEnhancedHue(EndpointId endpoint, uint16_t hue) -{ - emberAfWriteServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_ENHANCED_CURRENT_HUE_ATTRIBUTE_ID, - reinterpret_cast(&hue), ZCL_INT16U_ATTRIBUTE_TYPE); + uint8_t colorModeTransition; - writeHue(endpoint, static_cast(hue >> 8)); -} + if (oldColorMode == newColorMode) + { + return; + } + else + { + ColorControl::Attributes::SetEnhancedColorMode(endpoint, newColorMode); + ColorControl::Attributes::SetColorMode(endpoint, newColorMode); + } -static void writeSaturation(EndpointId endpoint, uint8_t saturation) -{ - emberAfWriteServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_CURRENT_SATURATION_ATTRIBUTE_ID, - reinterpret_cast(&saturation), ZCL_INT8U_ATTRIBUTE_TYPE); -} + colorModeTransition = static_cast((newColorMode << 4) + oldColorMode); -static void writeColorX(EndpointId endpoint, uint16_t colorX) -{ - emberAfWriteServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_CURRENT_X_ATTRIBUTE_ID, - reinterpret_cast(&colorX), ZCL_INT16U_ATTRIBUTE_TYPE); -} + // Note: It may be OK to not do anything here. + switch (colorModeTransition) + { + case ColorControlServer::Conversion::HSV_TO_CIE_XY: + computePwmFromXy(endpoint); + break; + case ColorControlServer::Conversion::TEMPERATURE_TO_CIE_XY: + computePwmFromXy(endpoint); + break; + case ColorControlServer::Conversion::CIE_XY_TO_HSV: + computePwmFromHsv(endpoint); + break; + case ColorControlServer::Conversion::TEMPERATURE_TO_HSV: + computePwmFromHsv(endpoint); + break; + case ColorControlServer::Conversion::HSV_TO_TEMPERATURE: + computePwmFromTemp(endpoint); + break; + case ColorControlServer::Conversion::CIE_XY_TO_TEMPERATURE: + computePwmFromTemp(endpoint); + break; -static void writeColorY(EndpointId endpoint, uint16_t colorY) -{ - emberAfWriteServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_CURRENT_Y_ATTRIBUTE_ID, - reinterpret_cast(&colorY), ZCL_INT16U_ATTRIBUTE_TYPE); + // for the following cases, there is no transition. + case ColorControlServer::Conversion::HSV_TO_HSV: + case ColorControlServer::Conversion::CIE_XY_TO_CIE_XY: + case ColorControlServer::Conversion::TEMPERATURE_TO_TEMPERATURE: + default: + return; + } } -// ------------------------------------------------------------------------- -// ****** callback section ******* - -#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV -static bool moveToHueAndSaturation(uint16_t hue, uint8_t saturation, uint16_t transitionTime, uint8_t optionsMask, - uint8_t optionsOverride, bool isEnhanced) +/** + * @brief calculates transition time frame currant sate and rate + * + * @param[in] p current Color16uTransitionState + * @param[in] rate + * @return uint16_t + */ +uint16_t ColorControlServer::computeTransitionTimeFromStateAndRate(ColorControlServer::Color16uTransitionState * p, uint16_t rate) { - // If isEnhanced is True this function was called by EnhancedMoveToHueAndSaturation command and the hue is a uint16 - // If isEnhanced is False this function was called by MoveToHueAndSaturation command and the hue is are a uint8 - - EndpointId endpoint = emberAfCurrentEndpoint(); - uint16_t currentHue = isEnhanced ? readEnhancedHue(endpoint) : static_cast(readHue(endpoint)); - bool moveUp; - - if (transitionTime == 0) - { - transitionTime++; - } - - // limit checking: hue and saturation are 0..254. Spec dictates we ignore - // this and report a malformed packet. - if ((!isEnhanced && hue > MAX_HUE_VALUE) || saturation > MAX_SATURATION_VALUE) - { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); - return true; - } + uint32_t transitionTime; + uint16_t max, min; - if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) + if (rate == 0) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + return MAX_INT16U_VALUE; } - // compute shortest direction - uint16_t halfWay = isEnhanced ? HALF_MAX_UINT16T : HALF_MAX_UINT8T; - if (hue > currentHue) + if (p->currentValue > p->finalValue) { - moveUp = (hue - currentHue) < halfWay; + max = p->currentValue; + min = p->finalValue; } else { - moveUp = (currentHue - hue) > halfWay; + max = p->finalValue; + min = p->currentValue; } - // New command. Need to stop any active transitions. - stopAllColorTransitions(); - - // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_HSV); - - // now, kick off the state machine. - initHueSat(endpoint); - colorHueTransitionState.isEnhancedHue = isEnhanced; + transitionTime = max - min; + transitionTime *= 10; + transitionTime /= rate; - if (isEnhanced) - { - colorHueTransitionState.initialEnhancedHue = currentHue; - colorHueTransitionState.currentEnhancedHue = currentHue; - colorHueTransitionState.finalEnhancedHue = hue; - } - else + if (transitionTime > MAX_INT16U_VALUE) { - colorHueTransitionState.initialHue = static_cast(currentHue); - colorHueTransitionState.currentHue = static_cast(currentHue); - colorHueTransitionState.finalHue = static_cast(hue); + return MAX_INT16U_VALUE; } - colorHueTransitionState.stepsRemaining = transitionTime; - colorHueTransitionState.stepsTotal = transitionTime; - colorHueTransitionState.endpoint = endpoint; - colorHueTransitionState.up = moveUp; - colorHueTransitionState.repeat = false; - - colorSaturationTransitionState.initialValue = readSaturation(endpoint); - colorSaturationTransitionState.currentValue = readSaturation(endpoint); - colorSaturationTransitionState.finalValue = saturation; - colorSaturationTransitionState.stepsRemaining = transitionTime; - colorSaturationTransitionState.stepsTotal = transitionTime; - colorSaturationTransitionState.endpoint = endpoint; - colorSaturationTransitionState.lowLimit = MIN_SATURATION_VALUE; - colorSaturationTransitionState.highLimit = MAX_SATURATION_VALUE; - - writeRemainingTime(endpoint, transitionTime); - - // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_HSV_CONTROL, UPDATE_TIME_MS); - - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + return (uint16_t) transitionTime; } -/** @brief Move To Hue And Saturation - * - * +/** + * @brief event control object for an endpoint * - * @param hue Ver.: always - * @param saturation Ver.: always - * @param transitionTime Ver.: always + * @param[in] endpoint + * @return EmberEventControl* */ -bool emberAfColorControlClusterMoveToHueAndSaturationCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t hue, - uint8_t saturation, uint16_t transitionTime, uint8_t optionsMask, - uint8_t optionsOverride) +EmberEventControl * ColorControlServer::getEventControl(EndpointId endpoint) { - return moveToHueAndSaturation(static_cast(hue), saturation, transitionTime, optionsMask, optionsOverride, false); + uint16_t index = emberAfFindClusterServerEndpointIndex(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID); + return &eventControls[index]; } -static bool moveHue(uint8_t moveMode, uint16_t rate, uint8_t optionsMask, uint8_t optionsOverride, bool isEnhanced) -{ - // If isEnhanced is True this function was called by EnhancedMoveHue command and rate is a uint16 value - // If isEnhanced is False this function was called by MoveHue command and rate is a uint8 value +/** @brief Compute Pwm from HSV + * + * This function is called from the color server when it is time for the PWMs to + * be driven with a new value from the color temperature. + * + * @param endpoint The identifying endpoint Ver.: always + */ +void ColorControlServer::computePwmFromTemp(EndpointId endpoint) {} - uint8_t currentHue = 0; - uint16_t currentEnhancedHue = 0; - EndpointId endpoint = emberAfCurrentEndpoint(); +/** @brief Compute Pwm from HSV + * + * This function is called from the color server when it is time for the PWMs to + * be driven with a new value from the HSV values. + * + * @param endpoint The identifying endpoint Ver.: always + */ +void ColorControlServer::computePwmFromHsv(EndpointId endpoint) {} - if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) - { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; - } +/** @brief Compute Pwm from HSV + * + * This function is called from the color server when it is time for the PWMs to + * be driven with a new value from the color X and color Y values. + * + * @param endpoint The identifying endpoint Ver.: always + */ +void ColorControlServer::computePwmFromXy(EndpointId endpoint) {} - // New command. Need to stop any active transitions. - stopAllColorTransitions(); +/** + * @brief Computes new color value based on current position + * + * @param p ColorHueTransitionState* + * @return true command mouvement is finished + * @return false command mouvement is not finished + */ +bool ColorControlServer::computeNewColor16uValue(ColorControlServer::Color16uTransitionState * p) +{ + uint32_t newValue32u; - if (moveMode == EMBER_ZCL_HUE_MOVE_MODE_STOP) + if (p->stepsRemaining == 0) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + return false; } - // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_HSV); + (p->stepsRemaining)--; - // now, kick off the state machine. - initHueSat(endpoint); - colorHueTransitionState.isEnhancedHue = isEnhanced; + ColorControl::Attributes::SetRemainingTime(p->endpoint, p->stepsRemaining); - if (isEnhanced) + // handle sign + if (p->finalValue == p->currentValue) { - currentEnhancedHue = readEnhancedHue(endpoint); - colorHueTransitionState.initialEnhancedHue = currentEnhancedHue; - colorHueTransitionState.currentEnhancedHue = currentEnhancedHue; + // do nothing } - else + else if (p->finalValue > p->initialValue) { - currentHue = readHue(endpoint); - colorHueTransitionState.initialHue = currentHue; - colorHueTransitionState.currentHue = currentHue; - } + newValue32u = ((uint32_t)(p->finalValue - p->initialValue)); + newValue32u *= ((uint32_t)(p->stepsRemaining)); + newValue32u /= ((uint32_t)(p->stepsTotal)); + p->currentValue = static_cast(p->finalValue - static_cast(newValue32u)); - if (moveMode == EMBER_ZCL_HUE_MOVE_MODE_UP) - { - if (isEnhanced) - { - colorHueTransitionState.finalEnhancedHue = addEnhancedHue(currentEnhancedHue, rate); - } - else + if (static_cast(newValue32u) > p->finalValue || p->currentValue > p->highLimit) { - colorHueTransitionState.finalHue = addHue(currentHue, static_cast(rate)); + p->currentValue = p->highLimit; } - - colorHueTransitionState.up = true; } - else if (moveMode == EMBER_ZCL_HUE_MOVE_MODE_DOWN) + else { - if (isEnhanced) - { - colorHueTransitionState.finalEnhancedHue = subtractEnhancedHue(currentEnhancedHue, rate); - } - else + newValue32u = ((uint32_t)(p->initialValue - p->finalValue)); + newValue32u *= ((uint32_t)(p->stepsRemaining)); + newValue32u /= ((uint32_t)(p->stepsTotal)); + p->currentValue = static_cast(p->finalValue + static_cast(newValue32u)); + + if (p->finalValue > UINT16_MAX - static_cast(newValue32u) || p->currentValue < p->lowLimit) { - colorHueTransitionState.finalHue = subtractHue(currentHue, static_cast(rate)); + p->currentValue = p->lowLimit; } - - colorHueTransitionState.up = false; } - else + + if (p->stepsRemaining == 0) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); + // we have completed our move. return true; } - colorHueTransitionState.stepsRemaining = TRANSITION_TIME_1S; - colorHueTransitionState.stepsTotal = TRANSITION_TIME_1S; - colorHueTransitionState.endpoint = endpoint; - colorHueTransitionState.repeat = true; - // hue movement can last forever. Indicate this with a remaining time of - // maxint. - writeRemainingTime(endpoint, MAX_INT16U_VALUE); - - colorSaturationTransitionState.stepsRemaining = 0; - - // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_HSV_CONTROL, UPDATE_TIME_MS); - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + return false; } -bool emberAfColorControlClusterMoveHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t moveMode, - uint8_t rate, uint8_t optionsMask, uint8_t optionsOverride) +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV + +/** + * @brief Returns ColorHueTransititionState associated to an endpoint + * + * @param[in] endpoint + * @return ColorControlServer::ColorHueTransitionState* + */ +ColorControlServer::ColorHueTransitionState * ColorControlServer::getColorHueTransitionState(EndpointId endpoint) { - return moveHue(moveMode, static_cast(rate), optionsMask, optionsOverride, false); + uint16_t index = emberAfFindClusterServerEndpointIndex(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID); + return &colorHueTransitionStates[index]; } -bool emberAfColorControlClusterMoveSaturationCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint8_t moveMode, - uint8_t rate, uint8_t optionsMask, uint8_t optionsOverride) +/** + * @brief Returns Color16uTransitionState for saturation associated to an endpoint + * + * @param[in] endpoint + * @return ColorControlServer::Color16uTransitionState* + */ +ColorControlServer::Color16uTransitionState * ColorControlServer::getSaturationTransitionState(EndpointId endpoint) { - EndpointId endpoint = emberAfCurrentEndpoint(); + uint16_t index = emberAfFindClusterServerEndpointIndex(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID); + return &colorSatTransitionStates[index]; +} - if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) - { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; - } +/** + * @brief Returns current saturation for a specified endpoint + * + * @param[in] endpoint + * @return uint8_t + */ +uint8_t ColorControlServer::getSaturation(EndpointId endpoint) +{ + uint8_t saturation = 0; + ColorControl::Attributes::GetCurrentSaturation(endpoint, &saturation); - uint16_t transitionTime; + return saturation; +} - // New command. Need to stop any active transitions. - stopAllColorTransitions(); +/** + * @brief Adds two hue values. + * If the sum is bigger than max hue value, max hue value is returned + * + * @param[in] hue1 + * @param[in] hue2 + * @return uint8_t + */ +uint8_t ColorControlServer::addHue(uint8_t hue1, uint8_t hue2) +{ + uint16_t hue16; - if (moveMode == EMBER_ZCL_SATURATION_MOVE_MODE_STOP || rate == 0) + hue16 = ((uint16_t) hue1); + hue16 = static_cast(hue16 + static_cast(hue2)); + + if (hue16 > MAX_HUE_VALUE) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + hue16 = static_cast(hue16 - MAX_HUE_VALUE - 1); } - // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_HSV); - - // now, kick off the state machine. - initHueSat(endpoint); + return ((uint8_t) hue16); +} - colorHueTransitionState.stepsRemaining = 0; +/** + * @brief Return difference betwen two hues. + * + * @param hue1 + * @param hue2 + * @return uint8_t + */ +uint8_t ColorControlServer::subtractHue(uint8_t hue1, uint8_t hue2) +{ + uint16_t hue16; - colorSaturationTransitionState.initialValue = readSaturation(endpoint); - colorSaturationTransitionState.currentValue = readSaturation(endpoint); - if (moveMode == EMBER_ZCL_SATURATION_MOVE_MODE_UP) - { - colorSaturationTransitionState.finalValue = MAX_SATURATION_VALUE; - } - else + hue16 = ((uint16_t) hue1); + if (hue2 > hue1) { - colorSaturationTransitionState.finalValue = MIN_SATURATION_VALUE; + hue16 = static_cast(hue16 + MAX_HUE_VALUE + 1); } - transitionTime = computeTransitionTimeFromStateAndRate(&colorSaturationTransitionState, rate); - - colorSaturationTransitionState.stepsRemaining = transitionTime; - colorSaturationTransitionState.stepsTotal = transitionTime; - colorSaturationTransitionState.endpoint = endpoint; - colorSaturationTransitionState.lowLimit = MIN_SATURATION_VALUE; - colorSaturationTransitionState.highLimit = MAX_SATURATION_VALUE; - - writeRemainingTime(endpoint, transitionTime); - - // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_HSV_CONTROL, UPDATE_TIME_MS); + hue16 = static_cast(hue16 - static_cast(hue2)); - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + return ((uint8_t) hue16); } -static bool moveToHue(uint16_t hue, uint8_t hueMoveMode, uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride, - bool isEnhanced) +/** + * @brief Returns sum of two saturations. If Overflow, return max saturation value + * + * @param[in] saturation1 + * @param[in] saturation2 + * @return uint8_t + */ +uint8_t ColorControlServer::addSaturation(uint8_t saturation1, uint8_t saturation2) { - // If isEnhanced is True this function was called by EnhancedMoveToHue and hue is a uint16 value - // If isEnhanced is False this function was called by MoveToHue command and hue is are a uint8 value - EndpointId endpoint = emberAfCurrentEndpoint(); + uint16_t saturation16; - if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) + saturation16 = ((uint16_t) saturation1); + saturation16 = static_cast(saturation16 + static_cast(saturation2)); + + if (saturation16 > MAX_SATURATION_VALUE) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + saturation16 = MAX_SATURATION_VALUE; } - uint16_t currentHue = isEnhanced ? readEnhancedHue(endpoint) : static_cast(readHue(endpoint)); - uint8_t direction; + return ((uint8_t) saturation16); +} - if (transitionTime == 0) +/** + * @brief Returns difference between two saturations. If Underflow, returns min saturation value + * + * @param saturation1 + * @param saturation2 + * @return uint8_t + */ +uint8_t ColorControlServer::subtractSaturation(uint8_t saturation1, uint8_t saturation2) +{ + if (saturation2 > saturation1) { - transitionTime++; + return MIN_SATURATION_VALUE; } - // Standard Hue limit checking: hue is 0..254. Spec dictates we ignore - // this and report a malformed packet. - if (!isEnhanced && (hue > MAX_HUE_VALUE)) - { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); - return true; - } + return static_cast(saturation1 - saturation2); +} - // For move to hue, the move modes are different from the other move commands. - // Need to translate from the move to hue transitions to the internal - // representation. - switch (hueMoveMode) - { - case EMBER_ZCL_HUE_DIRECTION_SHORTEST_DISTANCE: - if ((isEnhanced && (static_cast(currentHue - hue) > HALF_MAX_UINT16T)) || - (!isEnhanced && (static_cast(currentHue - hue) > HALF_MAX_UINT8T))) - { - direction = MOVE_MODE_UP; - } - else - { - direction = MOVE_MODE_DOWN; - } - break; - case EMBER_ZCL_HUE_DIRECTION_LONGEST_DISTANCE: - if ((isEnhanced && (static_cast(currentHue - hue) > HALF_MAX_UINT16T)) || - (!isEnhanced && (static_cast(currentHue - hue) > HALF_MAX_UINT8T))) - { - direction = MOVE_MODE_DOWN; - } - else - { - direction = MOVE_MODE_UP; - } - break; - case EMBER_ZCL_HUE_DIRECTION_UP: - direction = MOVE_MODE_UP; - break; - case EMBER_ZCL_HUE_DIRECTION_DOWN: - direction = MOVE_MODE_DOWN; - break; - default: - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); - return true; - } +/** + * @brief Returns sum of two enhanced hues + * + * @param[in] hue1 + * @param[in] hue2 + * @return uint16_t + */ +uint16_t ColorControlServer::addEnhancedHue(uint16_t hue1, uint16_t hue2) +{ + return static_cast(hue1 + hue2); +} - // New command. Need to stop any active transitions. - stopAllColorTransitions(); +/** + * @brief Returns difference of two enhanced hues + * + * @param[in] hue1 + * @param[in] hue2 + * @return uint16_t + */ +uint16_t ColorControlServer::subtractEnhancedHue(uint16_t hue1, uint16_t hue2) +{ + return static_cast(hue1 - hue2); +} - // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_HSV); +/** + * @brief Configures and launches color loop for a specified endpoint + * + * @param endpoint + * @param startFromStartHue True, start from StartEnhancedHue attribute. False, start from currentEnhancedHue + */ +void ColorControlServer::startColorLoop(EndpointId endpoint, uint8_t startFromStartHue) +{ + ColorHueTransitionState * colorHueTransitionState = getColorHueTransitionState(endpoint); + Color16uTransitionState * colorSaturationTransitionState = getSaturationTransitionState(endpoint); - // now, kick off the state machine. - initHueSat(endpoint); - colorHueTransitionState.isEnhancedHue = isEnhanced; + uint8_t direction = 0; + ColorControl::Attributes::GetColorLoopDirection(endpoint, &direction); - if (isEnhanced) + uint16_t time = 0x0019; + ColorControl::Attributes::GetColorLoopTime(endpoint, &time); + + uint16_t currentHue = 0; + ColorControl::Attributes::GetEnhancedCurrentHue(endpoint, ¤tHue); + + u_int16_t startHue = 0x2300; + if (startFromStartHue) { - colorHueTransitionState.initialEnhancedHue = readEnhancedHue(endpoint); - colorHueTransitionState.currentEnhancedHue = readEnhancedHue(endpoint); - colorHueTransitionState.finalEnhancedHue = hue; + ColorControl::Attributes::GetColorLoopStartEnhancedHue(endpoint, &startHue); } else { - colorHueTransitionState.initialHue = readHue(endpoint); - colorHueTransitionState.currentHue = readHue(endpoint); - colorHueTransitionState.finalHue = static_cast(hue); + startHue = currentHue; } - colorHueTransitionState.stepsRemaining = transitionTime; - colorHueTransitionState.stepsTotal = transitionTime; - colorHueTransitionState.endpoint = endpoint; - colorHueTransitionState.up = (direction == MOVE_MODE_UP); - colorHueTransitionState.repeat = false; - - colorSaturationTransitionState.stepsRemaining = 0; - - writeRemainingTime(endpoint, transitionTime); - - // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_HSV_CONTROL, UPDATE_TIME_MS); - - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; -} + ColorControl::Attributes::SetColorLoopStoredEnhancedHue(endpoint, currentHue); + ColorControl::Attributes::SetColorLoopActive(endpoint, true); -bool emberAfColorControlClusterMoveToHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t hue, - uint8_t hueMoveMode, uint16_t transitionTime, uint8_t optionsMask, - uint8_t optionsOverride) -{ - return moveToHue(static_cast(hue), hueMoveMode, transitionTime, optionsMask, optionsOverride, false); -} + initHueSat(endpoint, colorHueTransitionState, colorSaturationTransitionState); -bool emberAfColorControlClusterMoveToSaturationCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint8_t saturation, - uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride) -{ - EndpointId endpoint = emberAfCurrentEndpoint(); + colorHueTransitionState->isEnhancedHue = true; - if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) - { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; - } + colorHueTransitionState->initialEnhancedHue = startHue; + colorHueTransitionState->currentEnhancedHue = currentHue; - if (transitionTime == 0) + if (direction) { - transitionTime++; + colorHueTransitionState->finalEnhancedHue = static_cast(startHue - 1); } - - // limit checking: hue and saturation are 0..254. Spec dictates we ignore - // this and report a malformed packet. - if (saturation > MAX_SATURATION_VALUE) + else { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); - return true; + colorHueTransitionState->finalEnhancedHue = static_cast(startHue + 1); } - // New command. Need to stop any active transitions. - stopAllColorTransitions(); - - // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_HSV); + colorHueTransitionState->up = direction; + colorHueTransitionState->repeat = true; - // now, kick off the state machine. - initHueSat(endpoint); + colorHueTransitionState->stepsRemaining = static_cast(time * TRANSITION_TIME_1S); + colorHueTransitionState->stepsTotal = static_cast(time * TRANSITION_TIME_1S); + colorHueTransitionState->endpoint = endpoint; - colorHueTransitionState.stepsRemaining = 0; + ColorControl::Attributes::SetRemainingTime(endpoint, MAX_INT16U_VALUE); - colorSaturationTransitionState.initialValue = readSaturation(endpoint); - colorSaturationTransitionState.currentValue = readSaturation(endpoint); - colorSaturationTransitionState.finalValue = saturation; - colorSaturationTransitionState.stepsRemaining = transitionTime; - colorSaturationTransitionState.stepsTotal = transitionTime; - colorSaturationTransitionState.endpoint = endpoint; - colorSaturationTransitionState.lowLimit = MIN_SATURATION_VALUE; - colorSaturationTransitionState.highLimit = MAX_SATURATION_VALUE; + emberEventControlSetDelayMS(configureHSVEventControl(endpoint), UPDATE_TIME_MS); +} - writeRemainingTime(endpoint, transitionTime); +/** + * @brief Initialise memory structures for new command + * + * @param[in] endpoint + * @param[out] colorHueTransitionState + * @param[out] colorSatTransitionState + */ +void ColorControlServer::initHueSat(EndpointId endpoint, ColorControlServer::ColorHueTransitionState * colorHueTransitionState, + ColorControlServer::Color16uTransitionState * colorSatTransitionState) +{ + colorHueTransitionState->stepsRemaining = 0; + ColorControl::Attributes::GetCurrentHue(endpoint, &(colorHueTransitionState->currentHue)); + colorHueTransitionState->endpoint = endpoint; - // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_HSV_CONTROL, UPDATE_TIME_MS); + ColorControl::Attributes::GetEnhancedCurrentHue(endpoint, &(colorHueTransitionState->currentEnhancedHue)); + colorHueTransitionState->isEnhancedHue = false; - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + colorSatTransitionState->stepsRemaining = 0; + colorSatTransitionState->currentValue = getSaturation(endpoint); + colorSatTransitionState->endpoint = endpoint; } -static bool stepHue(uint8_t stepMode, uint16_t stepSize, uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride, - bool isEnhanced) +/** + * @brief Computes new hue value based on current position + * + * @param p ColorHueTransitionState* + * @return true command mouvement is finished + * @return false command mouvement is not finished + */ +bool ColorControlServer::computeNewHueValue(ColorControlServer::ColorHueTransitionState * p) { - // If isEnhanced is True this function was called by EnhancedStepHue and hue is a uint16 value - // If isEnhanced is False this function was called by StepHue command and hue is are a uint8 value - - EndpointId endpoint = emberAfCurrentEndpoint(); + uint32_t newHue32; + uint16_t newHue; - if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) + // exit with a false if hue is not currently moving + if (p->stepsRemaining == 0) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + return false; } - if (transitionTime == 0) + (p->stepsRemaining)--; + + if (p->repeat == false) { - transitionTime++; + ColorControl::Attributes::SetRemainingTime(p->endpoint, p->stepsRemaining); } - // New command. Need to stop any active transitions. - stopAllColorTransitions(); - - if (stepMode == MOVE_MODE_STOP) + // are we going up or down? + if ((p->isEnhancedHue && p->finalEnhancedHue == p->currentEnhancedHue) || (!p->isEnhancedHue && p->finalHue == p->currentHue)) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + // do nothing } - - // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_HSV); - - // now, kick off the state machine. - initHueSat(endpoint); - colorHueTransitionState.isEnhancedHue = isEnhanced; - - if (isEnhanced) + else if (p->up) { - colorHueTransitionState.initialEnhancedHue = colorHueTransitionState.currentEnhancedHue = readEnhancedHue(endpoint); - if (stepMode == MOVE_MODE_UP) + newHue32 = static_cast(p->isEnhancedHue ? subtractEnhancedHue(p->finalEnhancedHue, p->initialEnhancedHue) + : subtractHue(p->finalHue, p->initialHue)); + newHue32 *= static_cast(p->stepsRemaining); + newHue32 /= static_cast(p->stepsTotal); + + if (p->isEnhancedHue) { - colorHueTransitionState.finalEnhancedHue = addEnhancedHue(colorHueTransitionState.currentEnhancedHue, stepSize); - colorHueTransitionState.up = true; + p->currentEnhancedHue = subtractEnhancedHue(p->finalEnhancedHue, static_cast(newHue32)); } else { - colorHueTransitionState.finalEnhancedHue = subtractEnhancedHue(colorHueTransitionState.currentEnhancedHue, stepSize); - colorHueTransitionState.up = false; + p->currentHue = subtractHue(p->finalHue, static_cast(newHue32)); } } else { - colorHueTransitionState.initialHue = colorHueTransitionState.currentHue = readHue(endpoint); - if (stepMode == MOVE_MODE_UP) - { - colorHueTransitionState.finalHue = addHue(colorHueTransitionState.currentHue, static_cast(stepSize)); - colorHueTransitionState.up = true; - } - else - { - colorHueTransitionState.finalHue = subtractHue(colorHueTransitionState.currentHue, static_cast(stepSize)); - colorHueTransitionState.up = false; + newHue32 = static_cast(p->isEnhancedHue ? subtractEnhancedHue(p->initialEnhancedHue, p->finalEnhancedHue) + : subtractHue(p->initialHue, p->finalHue)); + newHue32 *= static_cast(p->stepsRemaining); + newHue32 /= static_cast(p->stepsTotal); + + if (p->isEnhancedHue) + { + p->currentEnhancedHue = addEnhancedHue(p->finalEnhancedHue, static_cast(newHue32)); + } + else + { + p->currentHue = addHue(p->finalHue, static_cast(newHue32)); } } - colorHueTransitionState.stepsRemaining = transitionTime; - colorHueTransitionState.stepsTotal = transitionTime; - colorHueTransitionState.endpoint = endpoint; - colorHueTransitionState.repeat = false; + if (p->stepsRemaining == 0) + { + if (p->repeat == false) + { + // we are performing a move to and not a move. + return true; + } + else + { + // Check if we are in a color loop. If not, we are in a moveHue + uint8_t isColorLoop = 0; + ColorControl::Attributes::GetColorLoopActive(p->endpoint, &isColorLoop); - colorSaturationTransitionState.stepsRemaining = 0; + if (isColorLoop) + { + p->currentEnhancedHue = p->initialEnhancedHue; + } + else + { + // we are performing a Hue move. Need to compute the new values for the + // next move period. + if (p->up) + { + if (p->isEnhancedHue) + { + newHue = subtractEnhancedHue(p->finalEnhancedHue, p->initialEnhancedHue); + newHue = addEnhancedHue(p->finalEnhancedHue, newHue); - writeRemainingTime(endpoint, transitionTime); + p->initialEnhancedHue = p->finalEnhancedHue; + p->finalEnhancedHue = newHue; + } + else + { + newHue = subtractHue(p->finalHue, p->initialHue); + newHue = addHue(p->finalHue, static_cast(newHue)); - // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_HSV_CONTROL, UPDATE_TIME_MS); + p->initialHue = p->finalHue; + p->finalHue = static_cast(newHue); + } + } + else + { + if (p->isEnhancedHue) + { + newHue = subtractEnhancedHue(p->initialEnhancedHue, p->finalEnhancedHue); + newHue = subtractEnhancedHue(p->finalEnhancedHue, newHue); - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + p->initialEnhancedHue = p->finalEnhancedHue; + p->finalEnhancedHue = newHue; + } + else + { + newHue = subtractHue(p->initialHue, p->finalHue); + newHue = subtractHue(p->finalHue, static_cast(newHue)); + + p->initialHue = p->finalHue; + p->finalHue = static_cast(newHue); + } + } + } + + p->stepsRemaining = p->stepsTotal; + } + } + return false; } -bool emberAfColorControlClusterStepHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t stepMode, - uint8_t stepSize, uint8_t transitionTime, uint8_t optionsMask, - uint8_t optionsOverride) +/** + * @brief Configures EnventControl callback when using HSV colors + * + * @param endpoint + */ +EmberEventControl * ColorControlServer::configureHSVEventControl(EndpointId endpoint) { - return stepHue(stepMode, static_cast(stepSize), static_cast(transitionTime), optionsMask, optionsOverride, - false); + EmberEventControl * controller = getEventControl(endpoint); + + controller->endpoint = endpoint; + controller->callback = &emberAfPluginColorControlServerHueSatTransitionEventHandler; + + return controller; } -bool emberAfColorControlClusterStepSaturationCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint8_t stepMode, - uint8_t stepSize, uint8_t transitionTime, uint8_t optionsMask, - uint8_t optionsOverride) +/** + * @brief Executes move Hue Command + * + * @param[in] moveMode + * @param[in] rate + * @param[in] optionsMask + * @param[in] optionsOverride + * @param[in] isEnhanced If True, function was called by EnhancedMoveHue command and rate is a uint16 value. If False function was + * called by MoveHue command and rate is a uint8 value + * @return true Success + * @return false Failed + */ +bool ColorControlServer::moveHueCommand(uint8_t moveMode, uint16_t rate, uint8_t optionsMask, uint8_t optionsOverride, + bool isEnhanced) { - EndpointId endpoint = emberAfCurrentEndpoint(); + uint8_t currentHue = 0; + uint16_t currentEnhancedHue = 0; + EndpointId endpoint = emberAfCurrentEndpoint(); + + ColorHueTransitionState * colorHueTransitionState = getColorHueTransitionState(endpoint); + Color16uTransitionState * colorSaturationTransitionState = getSaturationTransitionState(endpoint); if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { @@ -845,265 +745,346 @@ bool emberAfColorControlClusterStepSaturationCallback(EndpointId aEndpoint, app: return true; } - uint8_t currentSaturation = readSaturation(endpoint); - - if (transitionTime == 0) - { - transitionTime++; - } - // New command. Need to stop any active transitions. - stopAllColorTransitions(); + stopAllColorTransitions(endpoint); - if (stepMode == MOVE_MODE_STOP) + if (moveMode == EMBER_ZCL_HUE_MOVE_MODE_STOP) { emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; } // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_HSV); + handleModeSwitch(endpoint, ColorControlServer::ColorMode::COLOR_MODE_HSV); // now, kick off the state machine. - initHueSat(endpoint); + initHueSat(endpoint, colorHueTransitionState, colorSaturationTransitionState); - colorHueTransitionState.stepsRemaining = 0; + colorHueTransitionState->isEnhancedHue = isEnhanced; + if (isEnhanced) + { + ColorControl::Attributes::GetEnhancedCurrentHue(endpoint, ¤tEnhancedHue); + colorHueTransitionState->initialEnhancedHue = currentEnhancedHue; + colorHueTransitionState->currentEnhancedHue = currentEnhancedHue; + } + else + { + ColorControl::Attributes::GetCurrentHue(endpoint, ¤tHue); + colorHueTransitionState->initialHue = currentHue; + colorHueTransitionState->currentHue = currentHue; + } - colorSaturationTransitionState.initialValue = currentSaturation; - colorSaturationTransitionState.currentValue = currentSaturation; + if (moveMode == EMBER_ZCL_HUE_MOVE_MODE_UP) + { + if (isEnhanced) + { + colorHueTransitionState->finalEnhancedHue = addEnhancedHue(currentEnhancedHue, rate); + } + else + { + colorHueTransitionState->finalHue = addHue(currentHue, static_cast(rate)); + } - if (stepMode == MOVE_MODE_UP) + colorHueTransitionState->up = true; + } + else if (moveMode == EMBER_ZCL_HUE_MOVE_MODE_DOWN) { - colorSaturationTransitionState.finalValue = addSaturation(currentSaturation, stepSize); + if (isEnhanced) + { + colorHueTransitionState->finalEnhancedHue = subtractEnhancedHue(currentEnhancedHue, rate); + } + else + { + colorHueTransitionState->finalHue = subtractHue(currentHue, static_cast(rate)); + } + + colorHueTransitionState->up = false; } else { - colorSaturationTransitionState.finalValue = subtractSaturation(currentSaturation, stepSize); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); + return true; } - colorSaturationTransitionState.stepsRemaining = transitionTime; - colorSaturationTransitionState.stepsTotal = transitionTime; - colorSaturationTransitionState.endpoint = endpoint; - colorSaturationTransitionState.lowLimit = MIN_SATURATION_VALUE; - colorSaturationTransitionState.highLimit = MAX_SATURATION_VALUE; + colorHueTransitionState->stepsRemaining = TRANSITION_TIME_1S; + colorHueTransitionState->stepsTotal = TRANSITION_TIME_1S; + colorHueTransitionState->endpoint = endpoint; + colorHueTransitionState->repeat = true; - writeRemainingTime(endpoint, transitionTime); + // hue movement can last forever. Indicate this with a remaining time of maxint + ColorControl::Attributes::SetRemainingTime(endpoint, MAX_INT16U_VALUE); + colorSaturationTransitionState->stepsRemaining = 0; // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_HSV_CONTROL, UPDATE_TIME_MS); + emberEventControlSetDelayMS(configureHSVEventControl(endpoint), UPDATE_TIME_MS); emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; } -static uint8_t addSaturation(uint8_t saturation1, uint8_t saturation2) +/** + * @brief Executes move to hue command + * + * @param[in] hue + * @param[in] hueMoveMode + * @param[in] transitionTime + * @param[in] optionsMask + * @param[in] optionsOverride + * @param[in] isEnhanced If True, function was called by EnhancedMoveHue command and rate is a uint16 value. If False function was + * called by MoveHue command and rate is a uint8 value + * @return true Success + * @return false Failed + */ +bool ColorControlServer::moveToHueCommand(uint16_t hue, uint8_t hueMoveMode, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride, bool isEnhanced) { - uint16_t saturation16; - - saturation16 = ((uint16_t) saturation1); - saturation16 = static_cast(saturation16 + static_cast(saturation2)); + EndpointId endpoint = emberAfCurrentEndpoint(); + ColorHueTransitionState * colorHueTransitionState = getColorHueTransitionState(endpoint); + Color16uTransitionState * colorSaturationTransitionState = getSaturationTransitionState(endpoint); - if (saturation16 > MAX_SATURATION_VALUE) + if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { - saturation16 = MAX_SATURATION_VALUE; + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } - return ((uint8_t) saturation16); -} - -static uint8_t subtractSaturation(uint8_t saturation1, uint8_t saturation2) -{ - if (saturation2 > saturation1) + uint16_t currentHue = 0; + if (isEnhanced) { - return 0; + ColorControl::Attributes::GetEnhancedCurrentHue(endpoint, ¤tHue); } + else + { + uint8_t current8bitHue = 0; + ColorControl::Attributes::GetCurrentHue(endpoint, ¤t8bitHue); - return static_cast(saturation1 - saturation2); -} - -// any time we call a hue or saturation transition, we need to assume certain -// things about the hue and saturation data structures. This function will -// properly initialize them. -static void initHueSat(EndpointId endpoint) -{ - colorHueTransitionState.stepsRemaining = 0; - colorHueTransitionState.currentHue = readHue(endpoint); - colorHueTransitionState.endpoint = endpoint; - - colorHueTransitionState.currentEnhancedHue = readEnhancedHue(endpoint); - colorHueTransitionState.isEnhancedHue = false; - - colorSaturationTransitionState.stepsRemaining = 0; - colorSaturationTransitionState.currentValue = readSaturation(endpoint); - colorSaturationTransitionState.endpoint = endpoint; -} - -static uint8_t readHue(EndpointId endpoint) -{ - uint8_t hue; - emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_CURRENT_HUE_ATTRIBUTE_ID, - reinterpret_cast(&hue), sizeof(uint8_t)); - - return hue; -} - -static uint8_t readSaturation(EndpointId endpoint) -{ - uint8_t saturation; - emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_CURRENT_SATURATION_ATTRIBUTE_ID, - reinterpret_cast(&saturation), sizeof(uint8_t)); - - return saturation; -} -static uint16_t readEnhancedHue(EndpointId endpoint) -{ - uint16_t enhancedHue; - emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_ENHANCED_CURRENT_HUE_ATTRIBUTE_ID, - reinterpret_cast(&enhancedHue), sizeof(uint16_t)); - - return enhancedHue; -} - -#endif // #ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV + currentHue = static_cast(current8bitHue); + } -#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + uint8_t direction; -bool emberAfColorControlClusterMoveToColorCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint16_t colorX, - uint16_t colorY, uint16_t transitionTime, uint8_t optionsMask, - uint8_t optionsOverride) -{ - EndpointId endpoint = emberAfCurrentEndpoint(); + if (transitionTime == 0) + { + transitionTime++; + } - if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) + // Standard Hue limit checking: hue is 0..254. Spec dictates we ignore + // this and report a malformed packet. + if (!isEnhanced && (hue > MAX_HUE_VALUE)) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); return true; } - if (transitionTime == 0) + // For move to hue, the move modes are different from the other move commands. + // Need to translate from the move to hue transitions to the internal + // representation. + switch (hueMoveMode) { - transitionTime++; + case EMBER_ZCL_HUE_DIRECTION_SHORTEST_DISTANCE: + if ((isEnhanced && (static_cast(currentHue - hue) > HALF_MAX_UINT16T)) || + (!isEnhanced && (static_cast(currentHue - hue) > HALF_MAX_UINT8T))) + { + direction = MOVE_MODE_UP; + } + else + { + direction = MOVE_MODE_DOWN; + } + break; + case EMBER_ZCL_HUE_DIRECTION_LONGEST_DISTANCE: + if ((isEnhanced && (static_cast(currentHue - hue) > HALF_MAX_UINT16T)) || + (!isEnhanced && (static_cast(currentHue - hue) > HALF_MAX_UINT8T))) + { + direction = MOVE_MODE_DOWN; + } + else + { + direction = MOVE_MODE_UP; + } + break; + case EMBER_ZCL_HUE_DIRECTION_UP: + direction = MOVE_MODE_UP; + break; + case EMBER_ZCL_HUE_DIRECTION_DOWN: + direction = MOVE_MODE_DOWN; + break; + default: + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); + return true; } // New command. Need to stop any active transitions. - stopAllColorTransitions(); + stopAllColorTransitions(endpoint); // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_CIE_XY); + handleModeSwitch(endpoint, ColorControlServer::ColorMode::COLOR_MODE_HSV); // now, kick off the state machine. - colorXTransitionState.initialValue = readColorX(endpoint); - colorXTransitionState.currentValue = readColorX(endpoint); - colorXTransitionState.finalValue = colorX; - colorXTransitionState.stepsRemaining = transitionTime; - colorXTransitionState.stepsTotal = transitionTime; - colorXTransitionState.endpoint = endpoint; - colorXTransitionState.lowLimit = MIN_CIE_XY_VALUE; - colorXTransitionState.highLimit = MAX_CIE_XY_VALUE; - - colorYTransitionState.initialValue = readColorY(endpoint); - colorYTransitionState.currentValue = readColorY(endpoint); - colorYTransitionState.finalValue = colorY; - colorYTransitionState.stepsRemaining = transitionTime; - colorYTransitionState.stepsTotal = transitionTime; - colorYTransitionState.endpoint = endpoint; - colorYTransitionState.lowLimit = MIN_CIE_XY_VALUE; - colorYTransitionState.highLimit = MAX_CIE_XY_VALUE; - - writeRemainingTime(endpoint, transitionTime); + initHueSat(endpoint, colorHueTransitionState, colorSaturationTransitionState); + colorHueTransitionState->isEnhancedHue = isEnhanced; + + if (isEnhanced) + { + ColorControl::Attributes::GetEnhancedCurrentHue(endpoint, &(colorHueTransitionState->initialEnhancedHue)); + ColorControl::Attributes::GetEnhancedCurrentHue(endpoint, &(colorHueTransitionState->currentEnhancedHue)); + + colorHueTransitionState->finalEnhancedHue = hue; + } + else + { + ColorControl::Attributes::GetCurrentHue(endpoint, &(colorHueTransitionState->initialHue)); + ColorControl::Attributes::GetCurrentHue(endpoint, &(colorHueTransitionState->currentHue)); + + colorHueTransitionState->finalHue = static_cast(hue); + } + + colorHueTransitionState->stepsRemaining = transitionTime; + colorHueTransitionState->stepsTotal = transitionTime; + colorHueTransitionState->endpoint = endpoint; + colorHueTransitionState->up = (direction == MOVE_MODE_UP); + colorHueTransitionState->repeat = false; + colorSaturationTransitionState->stepsRemaining = 0; + + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTime); // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_XY_CONTROL, UPDATE_TIME_MS); + emberEventControlSetDelayMS(configureHSVEventControl(endpoint), UPDATE_TIME_MS); emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; } -bool emberAfColorControlClusterMoveColorCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, int16_t rateX, - int16_t rateY, uint8_t optionsMask, uint8_t optionsOverride) +/** + * @brief executes move to hue and saturatioan command + * + * @param[in] hue + * @param[in] saturation + * @param[in] transitionTime + * @param[in] optionsMask + * @param[in] optionsOverride + * @param[in] isEnhanced If True, function was called by EnhancedMoveHue command and rate is a uint16 value. If False function was + * called by MoveHue command and rate is a uint8 value + * @return true Success + * @return false Failed + */ +bool ColorControlServer::moveToHueAndSaturationCommand(uint16_t hue, uint8_t saturation, uint16_t transitionTime, + uint8_t optionsMask, uint8_t optionsOverride, bool isEnhanced) { - EndpointId endpoint = emberAfCurrentEndpoint(); + EndpointId endpoint = emberAfCurrentEndpoint(); + Color16uTransitionState * colorSaturationTransitionState = getSaturationTransitionState(endpoint); + ColorHueTransitionState * colorHueTransitionState = getColorHueTransitionState(endpoint); - if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) + uint16_t currentHue = 0; + if (isEnhanced) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + ColorControl::Attributes::GetEnhancedCurrentHue(endpoint, ¤tHue); } + else + { + uint8_t current8bitHue = 0; + ColorControl::Attributes::GetCurrentHue(endpoint, ¤t8bitHue); - uint16_t transitionTimeX, transitionTimeY; - uint16_t unsignedRate; + currentHue = static_cast(current8bitHue); + } - // New command. Need to stop any active transitions. - stopAllColorTransitions(); + bool moveUp; - if (rateX == 0 && rateY == 0) + if (transitionTime == 0) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + transitionTime++; } - // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_CIE_XY); - - // now, kick off the state machine. - colorXTransitionState.initialValue = readColorX(endpoint); - colorXTransitionState.currentValue = colorXTransitionState.initialValue; - if (rateX > 0) + // limit checking: hue and saturation are 0..254. Spec dictates we ignore + // this and report a malformed packet. + if ((!isEnhanced && hue > MAX_HUE_VALUE) || saturation > MAX_SATURATION_VALUE) { - colorXTransitionState.finalValue = MAX_CIE_XY_VALUE; - unsignedRate = (uint16_t) rateX; + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); + return true; } - else + + if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { - colorXTransitionState.finalValue = MIN_CIE_XY_VALUE; - unsignedRate = (uint16_t)(rateX * -1); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } - transitionTimeX = computeTransitionTimeFromStateAndRate(&colorXTransitionState, unsignedRate); - colorXTransitionState.stepsRemaining = transitionTimeX; - colorXTransitionState.stepsTotal = transitionTimeX; - colorXTransitionState.endpoint = endpoint; - colorXTransitionState.lowLimit = MIN_CIE_XY_VALUE; - colorXTransitionState.highLimit = MAX_CIE_XY_VALUE; - colorYTransitionState.initialValue = readColorY(endpoint); - colorYTransitionState.currentValue = colorYTransitionState.initialValue; - if (rateY > 0) + // compute shortest direction + uint16_t halfWay = isEnhanced ? HALF_MAX_UINT16T : HALF_MAX_UINT8T; + if (hue > currentHue) { - colorYTransitionState.finalValue = MAX_CIE_XY_VALUE; - unsignedRate = (uint16_t) rateY; + moveUp = (hue - currentHue) < halfWay; } else { - colorYTransitionState.finalValue = MIN_CIE_XY_VALUE; - unsignedRate = (uint16_t)(rateY * -1); + moveUp = (currentHue - hue) > halfWay; } - transitionTimeY = computeTransitionTimeFromStateAndRate(&colorYTransitionState, unsignedRate); - colorYTransitionState.stepsRemaining = transitionTimeY; - colorYTransitionState.stepsTotal = transitionTimeY; - colorYTransitionState.endpoint = endpoint; - colorYTransitionState.lowLimit = MIN_CIE_XY_VALUE; - colorYTransitionState.highLimit = MAX_CIE_XY_VALUE; - if (transitionTimeX < transitionTimeY) + // New command. Need to stop any active transitions. + stopAllColorTransitions(endpoint); + + // Handle color mode transition, if necessary. + handleModeSwitch(endpoint, ColorControlServer::ColorMode::COLOR_MODE_HSV); + + // now, kick off the state machine. + initHueSat(endpoint, colorHueTransitionState, colorSaturationTransitionState); + colorHueTransitionState->isEnhancedHue = isEnhanced; + + if (isEnhanced) { - writeRemainingTime(endpoint, transitionTimeX); + colorHueTransitionState->initialEnhancedHue = currentHue; + colorHueTransitionState->currentEnhancedHue = currentHue; + colorHueTransitionState->finalEnhancedHue = hue; } else { - writeRemainingTime(endpoint, transitionTimeY); + colorHueTransitionState->initialHue = static_cast(currentHue); + colorHueTransitionState->currentHue = static_cast(currentHue); + colorHueTransitionState->finalHue = static_cast(hue); } + colorHueTransitionState->stepsRemaining = transitionTime; + colorHueTransitionState->stepsTotal = transitionTime; + colorHueTransitionState->endpoint = endpoint; + colorHueTransitionState->up = moveUp; + colorHueTransitionState->repeat = false; + + colorSaturationTransitionState->initialValue = getSaturation(endpoint); + colorSaturationTransitionState->currentValue = getSaturation(endpoint); + colorSaturationTransitionState->finalValue = saturation; + colorSaturationTransitionState->stepsRemaining = transitionTime; + colorSaturationTransitionState->stepsTotal = transitionTime; + colorSaturationTransitionState->endpoint = endpoint; + colorSaturationTransitionState->lowLimit = MIN_SATURATION_VALUE; + colorSaturationTransitionState->highLimit = MAX_SATURATION_VALUE; + + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTime); + // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_XY_CONTROL, UPDATE_TIME_MS); + emberEventControlSetDelayMS(configureHSVEventControl(endpoint), UPDATE_TIME_MS); emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; } -bool emberAfColorControlClusterStepColorCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, int16_t stepX, - int16_t stepY, uint16_t transitionTime, uint8_t optionsMask, - uint8_t optionsOverride) +/** + * @brief Executes step hue command + * + * @param[in] stepMode + * @param[in] stepSize + * @param[in] transitionTime + * @param[in] optionsMask + * @param[in] optionsOverride + * @param[in] isEnhanced If True, function was called by EnhancedMoveHue command and rate is a uint16 value. If False function was + * called by MoveHue command and rate is a uint8 value + * @return true Success + * @return false Failed + */ +bool ColorControlServer::stepHueCommand(uint8_t stepMode, uint16_t stepSize, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride, bool isEnhanced) { - EndpointId endpoint = emberAfCurrentEndpoint(); + EndpointId endpoint = emberAfCurrentEndpoint(); + ColorHueTransitionState * colorHueTransitionState = getColorHueTransitionState(endpoint); + Color16uTransitionState * colorSaturationTransitionState = getSaturationTransitionState(endpoint); if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { @@ -1111,157 +1092,150 @@ bool emberAfColorControlClusterStepColorCallback(EndpointId aEndpoint, app::Comm return true; } - uint16_t colorX = findNewColorValueFromStep(readColorX(endpoint), stepX); - uint16_t colorY = findNewColorValueFromStep(readColorY(endpoint), stepY); - if (transitionTime == 0) { transitionTime++; } // New command. Need to stop any active transitions. - stopAllColorTransitions(); + stopAllColorTransitions(endpoint); + + if (stepMode == MOVE_MODE_STOP) + { + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; + } // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_CIE_XY); + handleModeSwitch(endpoint, COLOR_MODE_HSV); // now, kick off the state machine. - colorXTransitionState.initialValue = readColorX(endpoint); - colorXTransitionState.currentValue = readColorX(endpoint); - colorXTransitionState.finalValue = colorX; - colorXTransitionState.stepsRemaining = transitionTime; - colorXTransitionState.stepsTotal = transitionTime; - colorXTransitionState.endpoint = endpoint; - colorXTransitionState.lowLimit = MIN_CIE_XY_VALUE; - colorXTransitionState.highLimit = MAX_CIE_XY_VALUE; - - colorYTransitionState.initialValue = readColorY(endpoint); - colorYTransitionState.currentValue = readColorY(endpoint); - colorYTransitionState.finalValue = colorY; - colorYTransitionState.stepsRemaining = transitionTime; - colorYTransitionState.stepsTotal = transitionTime; - colorYTransitionState.endpoint = endpoint; - colorYTransitionState.lowLimit = MIN_CIE_XY_VALUE; - colorYTransitionState.highLimit = MAX_CIE_XY_VALUE; - - writeRemainingTime(endpoint, transitionTime); - - // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_XY_CONTROL, UPDATE_TIME_MS); - - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; -} - -static uint16_t findNewColorValueFromStep(uint16_t oldValue, int16_t step) -{ - uint16_t newValue; - int32_t newValueSigned; - - newValueSigned = ((int32_t) oldValue) + ((int32_t) step); + initHueSat(endpoint, colorHueTransitionState, colorSaturationTransitionState); + colorHueTransitionState->isEnhancedHue = isEnhanced; - if (newValueSigned < 0) - { - newValue = 0; - } - else if (newValueSigned > MAX_CIE_XY_VALUE) + if (isEnhanced) { - newValue = MAX_CIE_XY_VALUE; + ColorControl::Attributes::GetEnhancedCurrentHue(endpoint, &(colorHueTransitionState->currentEnhancedHue)); + colorHueTransitionState->initialEnhancedHue = colorHueTransitionState->currentEnhancedHue; + + if (stepMode == MOVE_MODE_UP) + { + colorHueTransitionState->finalEnhancedHue = addEnhancedHue(colorHueTransitionState->currentEnhancedHue, stepSize); + colorHueTransitionState->up = true; + } + else + { + colorHueTransitionState->finalEnhancedHue = subtractEnhancedHue(colorHueTransitionState->currentEnhancedHue, stepSize); + colorHueTransitionState->up = false; + } } else { - newValue = (uint16_t) newValueSigned; - } + ColorControl::Attributes::GetCurrentHue(endpoint, &(colorHueTransitionState->currentHue)); + colorHueTransitionState->initialHue = colorHueTransitionState->currentHue; - return newValue; -} + if (stepMode == MOVE_MODE_UP) + { + colorHueTransitionState->finalHue = addHue(colorHueTransitionState->currentHue, static_cast(stepSize)); + colorHueTransitionState->up = true; + } + else + { + colorHueTransitionState->finalHue = subtractHue(colorHueTransitionState->currentHue, static_cast(stepSize)); + colorHueTransitionState->up = false; + } + } -static uint16_t readColorX(EndpointId endpoint) -{ - uint16_t colorX; - emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_CURRENT_X_ATTRIBUTE_ID, - reinterpret_cast(&colorX), sizeof(uint16_t)); + colorHueTransitionState->stepsRemaining = transitionTime; + colorHueTransitionState->stepsTotal = transitionTime; + colorHueTransitionState->endpoint = endpoint; + colorHueTransitionState->repeat = false; + colorSaturationTransitionState->stepsRemaining = 0; - return colorX; -} + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTime); -static uint16_t readColorY(EndpointId endpoint) -{ - uint16_t colorY; - emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_CURRENT_Y_ATTRIBUTE_ID, - reinterpret_cast(&colorY), sizeof(uint16_t)); + // kick off the state machine: + emberEventControlSetDelayMS(configureHSVEventControl(endpoint), UPDATE_TIME_MS); - return colorY; + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } -#endif //#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY - -#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP - -static void moveToColorTemp(EndpointId endpoint, uint16_t colorTemperature, uint16_t transitionTime) +bool ColorControlServer::moveSaturationCommand(uint8_t moveMode, uint8_t rate, uint8_t optionsMask, uint8_t optionsOverride) { - uint16_t temperatureMin = readColorTemperatureMin(endpoint); - uint16_t temperatureMax = readColorTemperatureMax(endpoint); + EndpointId endpoint = emberAfCurrentEndpoint(); + ColorHueTransitionState * colorHueTransitionState = getColorHueTransitionState(endpoint); + Color16uTransitionState * colorSaturationTransitionState = getSaturationTransitionState(endpoint); - if (transitionTime == 0) + if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { - transitionTime++; + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } - // New command. Need to stop any active transitions. - stopAllColorTransitions(); + uint16_t transitionTime; - // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_TEMPERATURE); + // New command. Need to stop any active transitions. + stopAllColorTransitions(endpoint); - if (colorTemperature < temperatureMin) + if (moveMode == EMBER_ZCL_SATURATION_MOVE_MODE_STOP || rate == 0) { - colorTemperature = temperatureMin; + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } - if (colorTemperature > temperatureMax) - { - colorTemperature = temperatureMax; - } + // Handle color mode transition, if necessary. + handleModeSwitch(endpoint, COLOR_MODE_HSV); // now, kick off the state machine. - colorTempTransitionState.initialValue = readColorTemperature(endpoint); - colorTempTransitionState.currentValue = readColorTemperature(endpoint); - colorTempTransitionState.finalValue = colorTemperature; - colorTempTransitionState.stepsRemaining = transitionTime; - colorTempTransitionState.stepsTotal = transitionTime; - colorTempTransitionState.endpoint = endpoint; - colorTempTransitionState.lowLimit = temperatureMin; - colorTempTransitionState.highLimit = temperatureMax; - - // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_TEMP_CONTROL, UPDATE_TIME_MS); -} + initHueSat(endpoint, colorHueTransitionState, colorSaturationTransitionState); -bool emberAfColorControlClusterMoveToColorTemperatureCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, - uint16_t colorTemperature, uint16_t transitionTime, - uint8_t optionsMask, uint8_t optionsOverride) -{ - EndpointId endpoint = emberAfCurrentEndpoint(); + colorHueTransitionState->stepsRemaining = 0; - if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) + colorSaturationTransitionState->initialValue = getSaturation(endpoint); + colorSaturationTransitionState->currentValue = getSaturation(endpoint); + if (moveMode == EMBER_ZCL_SATURATION_MOVE_MODE_UP) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + colorSaturationTransitionState->finalValue = MAX_SATURATION_VALUE; + } + else + { + colorSaturationTransitionState->finalValue = MIN_SATURATION_VALUE; } - moveToColorTemp(endpoint, colorTemperature, transitionTime); + transitionTime = computeTransitionTimeFromStateAndRate(colorSaturationTransitionState, rate); + + colorSaturationTransitionState->stepsRemaining = transitionTime; + colorSaturationTransitionState->stepsTotal = transitionTime; + colorSaturationTransitionState->endpoint = endpoint; + colorSaturationTransitionState->lowLimit = MIN_SATURATION_VALUE; + colorSaturationTransitionState->highLimit = MAX_SATURATION_VALUE; + + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTime); + + // kick off the state machine: + emberEventControlSetDelayMS(configureHSVEventControl(endpoint), UPDATE_TIME_MS); emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; } -bool emberAfColorControlClusterMoveColorTemperatureCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, - uint8_t moveMode, uint16_t rate, uint16_t colorTemperatureMinimum, - uint16_t colorTemperatureMaximum, uint8_t optionsMask, - uint8_t optionsOverride) +/** + * @brief executes move to saturation command + * + * @param saturation + * @param transitionTime + * @param optionsMask + * @param optionsOverride + * @return true + * @return false + */ +bool ColorControlServer::moveToSaturationCommand(uint8_t saturation, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) { - EndpointId endpoint = emberAfCurrentEndpoint(); + EndpointId endpoint = emberAfCurrentEndpoint(); + ColorHueTransitionState * colorHueTransitionState = getColorHueTransitionState(endpoint); + Color16uTransitionState * colorSaturationTransitionState = getSaturationTransitionState(endpoint); if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { @@ -1269,90 +1243,54 @@ bool emberAfColorControlClusterMoveColorTemperatureCallback(EndpointId aEndpoint return true; } - uint16_t tempPhysicalMin = MIN_TEMPERATURE_VALUE; - Attributes::GetColorTempPhysicalMin(endpoint, &tempPhysicalMin); - - uint16_t tempPhysicalMax = MAX_TEMPERATURE_VALUE; - Attributes::GetColorTempPhysicalMax(endpoint, &tempPhysicalMax); - - uint16_t transitionTime; - - // New command. Need to stop any active transitions. - stopAllColorTransitions(); - - if (moveMode == MOVE_MODE_STOP) + if (transitionTime == 0) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; + transitionTime++; } - if (rate == 0) + // limit checking: hue and saturation are 0..254. Spec dictates we ignore + // this and report a malformed packet. + if (saturation > MAX_SATURATION_VALUE) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_FIELD); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); return true; } - if (colorTemperatureMinimum < tempPhysicalMin) - { - colorTemperatureMinimum = tempPhysicalMin; - } - if (colorTemperatureMaximum > tempPhysicalMax) - { - colorTemperatureMaximum = tempPhysicalMax; - } + // New command. Need to stop any active transitions. + stopAllColorTransitions(endpoint); // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_TEMPERATURE); + handleModeSwitch(endpoint, COLOR_MODE_HSV); // now, kick off the state machine. - colorTempTransitionState.initialValue = 0; - Attributes::GetColorTemperature(endpoint, &colorTempTransitionState.initialValue); - colorTempTransitionState.currentValue = colorTempTransitionState.initialValue; + initHueSat(endpoint, colorHueTransitionState, colorSaturationTransitionState); - if (moveMode == MOVE_MODE_UP) - { - if (tempPhysicalMax > colorTemperatureMaximum) - { - colorTempTransitionState.finalValue = colorTemperatureMaximum; - } - else - { - colorTempTransitionState.finalValue = tempPhysicalMax; - } - } - else - { - if (tempPhysicalMin < colorTemperatureMinimum) - { - colorTempTransitionState.finalValue = colorTemperatureMinimum; - } - else - { - colorTempTransitionState.finalValue = tempPhysicalMin; - } - } - transitionTime = computeTransitionTimeFromStateAndRate(&colorTempTransitionState, rate); - colorTempTransitionState.stepsRemaining = transitionTime; - colorTempTransitionState.stepsTotal = transitionTime; - colorTempTransitionState.endpoint = endpoint; - colorTempTransitionState.lowLimit = colorTemperatureMinimum; - colorTempTransitionState.highLimit = colorTemperatureMaximum; + colorHueTransitionState->stepsRemaining = 0; - Attributes::SetRemainingTime(endpoint, transitionTime); + colorSaturationTransitionState->initialValue = getSaturation(endpoint); + colorSaturationTransitionState->currentValue = getSaturation(endpoint); + colorSaturationTransitionState->finalValue = saturation; + colorSaturationTransitionState->stepsRemaining = transitionTime; + colorSaturationTransitionState->stepsTotal = transitionTime; + colorSaturationTransitionState->endpoint = endpoint; + colorSaturationTransitionState->lowLimit = MIN_SATURATION_VALUE; + colorSaturationTransitionState->highLimit = MAX_SATURATION_VALUE; + + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTime); // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_TEMP_CONTROL, UPDATE_TIME_MS); + emberEventControlSetDelayMS(configureHSVEventControl(endpoint), UPDATE_TIME_MS); emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; } -bool emberAfColorControlClusterStepColorTemperatureCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, - uint8_t stepMode, uint16_t stepSize, uint16_t transitionTime, - uint16_t colorTemperatureMinimum, uint16_t colorTemperatureMaximum, - uint8_t optionsMask, uint8_t optionsOverride) +bool ColorControlServer::stepSaturationCommand(uint8_t stepMode, uint8_t stepSize, uint8_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) { - EndpointId endpoint = emberAfCurrentEndpoint(); + EndpointId endpoint = emberAfCurrentEndpoint(); + ColorHueTransitionState * colorHueTransitionState = getColorHueTransitionState(endpoint); + Color16uTransitionState * colorSaturationTransitionState = getSaturationTransitionState(endpoint); if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { @@ -1360,11 +1298,7 @@ bool emberAfColorControlClusterStepColorTemperatureCallback(EndpointId aEndpoint return true; } - uint16_t tempPhysicalMin = MIN_TEMPERATURE_VALUE; - Attributes::GetColorTempPhysicalMin(endpoint, &tempPhysicalMin); - - uint16_t tempPhysicalMax = MAX_TEMPERATURE_VALUE; - Attributes::GetColorTempPhysicalMax(endpoint, &tempPhysicalMax); + uint8_t currentSaturation = getSaturation(endpoint); if (transitionTime == 0) { @@ -1372,7 +1306,7 @@ bool emberAfColorControlClusterStepColorTemperatureCallback(EndpointId aEndpoint } // New command. Need to stop any active transitions. - stopAllColorTransitions(); + stopAllColorTransitions(endpoint); if (stepMode == MOVE_MODE_STOP) { @@ -1380,203 +1314,45 @@ bool emberAfColorControlClusterStepColorTemperatureCallback(EndpointId aEndpoint return true; } - if (colorTemperatureMinimum < tempPhysicalMin) - { - colorTemperatureMinimum = tempPhysicalMin; - } - if (colorTemperatureMaximum > tempPhysicalMax) - { - colorTemperatureMaximum = tempPhysicalMax; - } - // Handle color mode transition, if necessary. - handleModeSwitch(endpoint, COLOR_MODE_TEMPERATURE); + handleModeSwitch(endpoint, COLOR_MODE_HSV); // now, kick off the state machine. - colorTempTransitionState.initialValue = 0; - Attributes::GetColorTemperature(endpoint, &colorTempTransitionState.initialValue); - colorTempTransitionState.currentValue = colorTempTransitionState.initialValue; + initHueSat(endpoint, colorHueTransitionState, colorSaturationTransitionState); + + colorHueTransitionState->stepsRemaining = 0; + + colorSaturationTransitionState->initialValue = currentSaturation; + colorSaturationTransitionState->currentValue = currentSaturation; if (stepMode == MOVE_MODE_UP) { - uint32_t finalValue32u = static_cast(colorTempTransitionState.initialValue) + static_cast(stepSize); - if (finalValue32u > UINT16_MAX) - { - colorTempTransitionState.finalValue = UINT16_MAX; - } - else - { - colorTempTransitionState.finalValue = static_cast(finalValue32u); - } + colorSaturationTransitionState->finalValue = addSaturation(currentSaturation, stepSize); } else { - uint32_t finalValue32u = static_cast(colorTempTransitionState.initialValue) - static_cast(stepSize); - if (finalValue32u > UINT16_MAX) - { - colorTempTransitionState.finalValue = 0; - } - else - { - colorTempTransitionState.finalValue = static_cast(finalValue32u); - } + colorSaturationTransitionState->finalValue = subtractSaturation(currentSaturation, stepSize); } - colorTempTransitionState.stepsRemaining = transitionTime; - colorTempTransitionState.stepsTotal = transitionTime; - colorTempTransitionState.endpoint = endpoint; - colorTempTransitionState.lowLimit = colorTemperatureMinimum; - colorTempTransitionState.highLimit = colorTemperatureMaximum; + colorSaturationTransitionState->stepsRemaining = transitionTime; + colorSaturationTransitionState->stepsTotal = transitionTime; + colorSaturationTransitionState->endpoint = endpoint; + colorSaturationTransitionState->lowLimit = MIN_SATURATION_VALUE; + colorSaturationTransitionState->highLimit = MAX_SATURATION_VALUE; - Attributes::SetRemainingTime(endpoint, transitionTime); + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTime); // kick off the state machine: - emberEventControlSetDelayMS(&COLOR_TEMP_CONTROL, UPDATE_TIME_MS); + emberEventControlSetDelayMS(configureHSVEventControl(endpoint), UPDATE_TIME_MS); emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; } -void emberAfPluginLevelControlCoupledColorTempChangeCallback(EndpointId endpoint) +bool ColorControlServer::colorLoopCommand(uint8_t updateFlags, uint8_t action, uint8_t direction, uint16_t time, uint16_t startHue, + uint8_t optionsMask, uint8_t optionsOverride) { - // ZCL 5.2.2.1.1 Coupling color temperature to Level Control - // - // If the Level Control for Lighting cluster identifier 0x0008 is supported - // on the same endpoint as the Color Control cluster and color temperature is - // supported, it is possible to couple changes in the current level to the - // color temperature. - // - // The CoupleColorTempToLevel bit of the Options attribute of the Level - // Control cluster indicates whether the color temperature is to be linked - // with the CurrentLevel attribute in the Level Control cluster. - // - // If the CoupleColorTempToLevel bit of the Options attribute of the Level - // Control cluster is equal to 1 and the ColorMode or EnhancedColorMode - // attribute is set to 0x02 (color temperature) then a change in the - // CurrentLevel attribute SHALL affect the ColorTemperatureMireds attribute. - // This relationship is manufacturer specific, with the qualification that - // the maximum value of the CurrentLevel attribute SHALL correspond to a - // ColorTemperatureMired attribute value equal to the - // CoupleColorTempToLevelMinMireds attribute. This relationship is one-way so - // a change to the ColorTemperatureMireds attribute SHALL NOT have any effect - // on the CurrentLevel attribute. - // - // In order to simulate the behavior of an incandescent bulb, a low value of - // the CurrentLevel attribute SHALL be associated with a high value of the - // ColorTemperatureMireds attribute (i.e., a low value of color temperature - // in kelvins). - // - // If the CoupleColorTempToLevel bit of the Options attribute of the Level - // Control cluster is equal to 0, there SHALL be no link between color - // temperature and current level. - - if (!emberAfContainsServer(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID)) - { - return; - } - - if (readColorMode(endpoint) == COLOR_MODE_TEMPERATURE) - { - uint16_t tempCoupleMin = readColorTemperatureCoupleToLevelMin(endpoint); - uint16_t tempPhysMax = readColorTemperatureMax(endpoint); - uint8_t currentLevel = readLevelControlCurrentLevel(endpoint); - - // Scale color temp setting between the coupling min and the physical max. - // Note that mireds varies inversely with level: low level -> high mireds. - // Peg min/MAX level to MAX/min mireds, otherwise interpolate. - uint16_t newColorTemp; - if (currentLevel <= MIN_CURRENT_LEVEL) - { - newColorTemp = tempPhysMax; - } - else if (currentLevel >= MAX_CURRENT_LEVEL) - { - newColorTemp = tempCoupleMin; - } - else - { - uint32_t tempDelta = (((uint32_t) tempPhysMax - (uint32_t) tempCoupleMin) * currentLevel) / - (uint32_t)(MAX_CURRENT_LEVEL - MIN_CURRENT_LEVEL + 1); - newColorTemp = (uint16_t)((uint32_t) tempPhysMax - tempDelta); - } - - // Apply new color temp. - moveToColorTemp(endpoint, newColorTemp, 0); - } -} - -#endif //#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP - -bool emberAfColorControlClusterStopMoveStepCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint8_t optionsMask, - uint8_t optionsOverride) -{ - // Received a stop command. This is all we need to do. - EndpointId endpoint = emberAfCurrentEndpoint(); - - if (shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) - { - stopAllColorTransitions(); - } - - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); - return true; -} - -static void startColorLoop(EndpointId endpoint, uint8_t startFromStartHue) -{ - uint8_t direction = 0; - Attributes::GetColorLoopDirection(endpoint, &direction); - - uint16_t time = 0x0019; - Attributes::GetColorLoopTime(endpoint, &time); - - uint16_t currentHue = 0; - Attributes::GetEnhancedCurrentHue(endpoint, ¤tHue); - - uint16_t startHue = 0x2300; - if (startFromStartHue) - { - Attributes::GetColorLoopStartEnhancedHue(endpoint, &startHue); - } - else - { - startHue = currentHue; - } - - Attributes::SetColorLoopStoredEnhancedHue(endpoint, currentHue); - Attributes::SetColorLoopActive(endpoint, true); - - initHueSat(endpoint); - - colorHueTransitionState.isEnhancedHue = true; - - colorHueTransitionState.initialEnhancedHue = startHue; - colorHueTransitionState.currentEnhancedHue = currentHue; - - if (direction) - { - colorHueTransitionState.finalEnhancedHue = static_cast(startHue - 1); - } - else - { - colorHueTransitionState.finalEnhancedHue = static_cast(startHue + 1); - } - - colorHueTransitionState.up = direction; - colorHueTransitionState.repeat = true; - - colorHueTransitionState.stepsRemaining = static_cast(time * TRANSITION_TIME_1S); - colorHueTransitionState.stepsTotal = static_cast(time * TRANSITION_TIME_1S); - colorHueTransitionState.endpoint = endpoint; - - Attributes::SetRemainingTime(endpoint, MAX_INT16U_VALUE); - emberEventControlSetDelayMS(&COLOR_HSV_CONTROL, UPDATE_TIME_MS); -} - -bool emberAfColorControlClusterColorLoopSetCallback(chip::EndpointId aEndpoint, chip::app::CommandHandler * commandObj, - uint8_t updateFlags, uint8_t action, uint8_t direction, uint16_t time, - uint16_t startHue, uint8_t optionsMask, uint8_t optionsOverride) -{ - EndpointId endpoint = emberAfCurrentEndpoint(); + EndpointId endpoint = emberAfCurrentEndpoint(); + ColorHueTransitionState * colorHueTransitionState = getColorHueTransitionState(endpoint); if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { @@ -1585,58 +1361,58 @@ bool emberAfColorControlClusterColorLoopSetCallback(chip::EndpointId aEndpoint, } uint8_t isColorLoopActive = 0; - Attributes::GetColorLoopActive(endpoint, &isColorLoopActive); + ColorControl::Attributes::GetColorLoopActive(endpoint, &isColorLoopActive); uint8_t deactiveColorLoop = (updateFlags & EMBER_AF_COLOR_LOOP_UPDATE_FLAGS_UPDATE_ACTION) && (action == EMBER_ZCL_COLOR_LOOP_ACTION_DEACTIVATE); if (updateFlags & EMBER_AF_COLOR_LOOP_UPDATE_FLAGS_UPDATE_DIRECTION) { - Attributes::SetColorLoopDirection(endpoint, direction); + ColorControl::Attributes::SetColorLoopDirection(endpoint, direction); // Checks if color loop is active and stays active if (isColorLoopActive && !deactiveColorLoop) { - colorHueTransitionState.up = direction; - colorHueTransitionState.initialEnhancedHue = colorHueTransitionState.currentEnhancedHue; + colorHueTransitionState->up = direction; + colorHueTransitionState->initialEnhancedHue = colorHueTransitionState->currentEnhancedHue; if (direction) { - colorHueTransitionState.finalEnhancedHue = static_cast(colorHueTransitionState.initialEnhancedHue - 1); + colorHueTransitionState->finalEnhancedHue = static_cast(colorHueTransitionState->initialEnhancedHue - 1); } else { - colorHueTransitionState.finalEnhancedHue = static_cast(colorHueTransitionState.initialEnhancedHue + 1); + colorHueTransitionState->finalEnhancedHue = static_cast(colorHueTransitionState->initialEnhancedHue + 1); } - colorHueTransitionState.stepsRemaining = colorHueTransitionState.stepsTotal; + colorHueTransitionState->stepsRemaining = colorHueTransitionState->stepsTotal; } } if (updateFlags & EMBER_AF_COLOR_LOOP_UPDATE_FLAGS_UPDATE_TIME) { - Attributes::SetColorLoopTime(endpoint, time); + ColorControl::Attributes::SetColorLoopTime(endpoint, time); // Checks if color loop is active and stays active if (isColorLoopActive && !deactiveColorLoop) { - colorHueTransitionState.stepsTotal = static_cast(time * TRANSITION_TIME_1S); - colorHueTransitionState.initialEnhancedHue = colorHueTransitionState.currentEnhancedHue; + colorHueTransitionState->stepsTotal = static_cast(time * TRANSITION_TIME_1S); + colorHueTransitionState->initialEnhancedHue = colorHueTransitionState->currentEnhancedHue; - if (colorHueTransitionState.up) + if (colorHueTransitionState->up) { - colorHueTransitionState.finalEnhancedHue = static_cast(colorHueTransitionState.initialEnhancedHue - 1); + colorHueTransitionState->finalEnhancedHue = static_cast(colorHueTransitionState->initialEnhancedHue - 1); } else { - colorHueTransitionState.finalEnhancedHue = static_cast(colorHueTransitionState.initialEnhancedHue + 1); + colorHueTransitionState->finalEnhancedHue = static_cast(colorHueTransitionState->initialEnhancedHue + 1); } - colorHueTransitionState.stepsRemaining = colorHueTransitionState.stepsTotal; + colorHueTransitionState->stepsRemaining = colorHueTransitionState->stepsTotal; } } if (updateFlags & EMBER_AF_COLOR_LOOP_UPDATE_FLAGS_UPDATE_START_HUE) { - Attributes::SetColorLoopStartEnhancedHue(endpoint, startHue); + ColorControl::Attributes::SetColorLoopStartEnhancedHue(endpoint, startHue); } if (updateFlags & EMBER_AF_COLOR_LOOP_UPDATE_FLAGS_UPDATE_ACTION) @@ -1645,13 +1421,13 @@ bool emberAfColorControlClusterColorLoopSetCallback(chip::EndpointId aEndpoint, { if (isColorLoopActive) { - stopAllColorTransitions(); + stopAllColorTransitions(endpoint); - Attributes::SetColorLoopActive(endpoint, false); + ColorControl::Attributes::SetColorLoopActive(endpoint, false); uint16_t storedEnhancedHue = 0; - Attributes::GetColorLoopStoredEnhancedHue(endpoint, &storedEnhancedHue); - Attributes::SetEnhancedCurrentHue(endpoint, storedEnhancedHue); + ColorControl::Attributes::GetColorLoopStoredEnhancedHue(endpoint, &storedEnhancedHue); + ColorControl::Attributes::SetEnhancedCurrentHue(endpoint, storedEnhancedHue); } else { @@ -1677,574 +1453,1027 @@ bool emberAfColorControlClusterColorLoopSetCallback(chip::EndpointId aEndpoint, return true; } -bool emberAfColorControlClusterEnhancedMoveHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t moveMode, - uint16_t rate, uint8_t optionsMask, uint8_t optionsOverride) +/** + * @brief updates Hue and saturation after timer is finished + * + * @param endpoint + */ +void ColorControlServer::updateHueSatCommand(EndpointId endpoint) { - return moveHue(moveMode, rate, optionsMask, optionsOverride, true); -} + bool limitReached1, limitReached2; -bool emberAfColorControlClusterEnhancedMoveToHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, - uint16_t enhancedHue, uint8_t direction, uint16_t transitionTime, - uint8_t optionsMask, uint8_t optionsOverride) -{ - return moveToHue(enhancedHue, direction, transitionTime, optionsMask, optionsOverride, true); -} + ColorHueTransitionState * colorHueTransitionState = getColorHueTransitionState(endpoint); + Color16uTransitionState * colorSaturationTransitionState = getSaturationTransitionState(endpoint); -bool emberAfColorControlClusterEnhancedMoveToHueAndSaturationCallback(EndpointId endpoint, app::CommandHandler * commandObj, - uint16_t enhancedHue, uint8_t saturation, - uint16_t transitionTime, uint8_t optionsMask, - uint8_t optionsOverride) -{ - return moveToHueAndSaturation(enhancedHue, saturation, transitionTime, optionsMask, optionsOverride, true); -} + limitReached1 = computeNewHueValue(colorHueTransitionState); + limitReached2 = computeNewColor16uValue(colorSaturationTransitionState); -bool emberAfColorControlClusterEnhancedStepHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t stepMode, - uint16_t stepSize, uint16_t transitionTime, uint8_t optionsMask, - uint8_t optionsOverride) -{ - return stepHue(stepMode, stepSize, transitionTime, optionsMask, optionsOverride, true); + if (limitReached1 || limitReached2) + { + stopAllColorTransitions(endpoint); + } + else + { + emberEventControlSetDelayMS(configureHSVEventControl(endpoint), UPDATE_TIME_MS); + } + + if (colorHueTransitionState->isEnhancedHue) + { + ColorControl::Attributes::SetEnhancedCurrentHue(endpoint, colorHueTransitionState->currentEnhancedHue); + ColorControl::Attributes::SetCurrentHue(endpoint, static_cast(colorHueTransitionState->currentEnhancedHue >> 8)); + } + else + { + ColorControl::Attributes::SetCurrentHue(colorHueTransitionState->endpoint, colorHueTransitionState->currentHue); + } + + ColorControl::Attributes::SetCurrentSaturation(colorSaturationTransitionState->endpoint, + (uint8_t) colorSaturationTransitionState->currentValue); + if (colorHueTransitionState->isEnhancedHue) + { + emberAfColorControlClusterPrintln("Enhanced Hue %d Saturation %d endpoint %d", colorHueTransitionState->currentEnhancedHue, + colorSaturationTransitionState->currentValue, endpoint); + } + else + { + emberAfColorControlClusterPrintln("Hue %d Saturation %d endpoint %d", colorHueTransitionState->currentHue, + colorSaturationTransitionState->currentValue, endpoint); + } + computePwmFromHsv(endpoint); } -// **************** transition state machines *********** +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV -static void stopAllColorTransitions(void) +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + +/** + * @brief Returns Color16uTransitionState for X color associated to an endpoint + * + * @param endpoint + * @return ColorControlServer::Color16uTransitionState* + */ +ColorControlServer::Color16uTransitionState * ColorControlServer::getXTransitionState(EndpointId endpoint) { - emberEventControlSetInactive(&COLOR_TEMP_CONTROL); - emberEventControlSetInactive(&COLOR_XY_CONTROL); - emberEventControlSetInactive(&COLOR_HSV_CONTROL); + uint16_t index = emberAfFindClusterServerEndpointIndex(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID); + return &colorXtransitionStates[index]; } -void emberAfPluginColorControlServerStopTransition(void) +/** + * @brief Returns Color16uTransitionState for Y color associated to an endpoint + * + * @param endpoint + * @return ColorControlServer::Color16uTransitionState* + */ +ColorControlServer::Color16uTransitionState * ColorControlServer::getYTransitionState(EndpointId endpoint) { - stopAllColorTransitions(); + uint16_t index = emberAfFindClusterServerEndpointIndex(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID); + return &colorYtransitionStates[index]; } -// The specification says that if we are transitioning from one color mode -// into another, we need to compute the new mode's attribute values from the -// old mode. However, it also says that if the old mode doesn't translate into -// the new mode, this must be avoided. -// I am putting in this function to compute the new attributes based on the old -// color mode. -static void handleModeSwitch(EndpointId endpoint, uint8_t newColorMode) +uint16_t ColorControlServer::findNewColorValueFromStep(uint16_t oldValue, int16_t step) { - uint8_t oldColorMode = readColorMode(endpoint); - uint8_t colorModeTransition; + uint16_t newValue; + int32_t newValueSigned; - if (oldColorMode == newColorMode) + newValueSigned = ((int32_t) oldValue) + ((int32_t) step); + + if (newValueSigned < 0) { - return; + newValue = 0; + } + else if (newValueSigned > MAX_CIE_XY_VALUE) + { + newValue = MAX_CIE_XY_VALUE; } else { - writeColorMode(endpoint, newColorMode); + newValue = (uint16_t) newValueSigned; } - colorModeTransition = static_cast((newColorMode << 4) + oldColorMode); + return newValue; +} - // Note: It may be OK to not do anything here. - switch (colorModeTransition) - { - case HSV_TO_CIE_XY: - emberAfPluginColorControlServerComputePwmFromXyCallback(endpoint); - break; - case TEMPERATURE_TO_CIE_XY: - emberAfPluginColorControlServerComputePwmFromXyCallback(endpoint); - break; - case CIE_XY_TO_HSV: - emberAfPluginColorControlServerComputePwmFromHsvCallback(endpoint); - break; - case TEMPERATURE_TO_HSV: - emberAfPluginColorControlServerComputePwmFromHsvCallback(endpoint); - break; - case HSV_TO_TEMPERATURE: - emberAfPluginColorControlServerComputePwmFromTempCallback(endpoint); - break; - case CIE_XY_TO_TEMPERATURE: - emberAfPluginColorControlServerComputePwmFromTempCallback(endpoint); - break; +/** + * @brief Configures EnventControl callback when using XY colors + * + * @param endpoint + */ +EmberEventControl * ColorControlServer::configureXYEventControl(EndpointId endpoint) +{ + EmberEventControl * controller = getEventControl(endpoint); - // for the following cases, there is no transition. - case HSV_TO_HSV: - case CIE_XY_TO_CIE_XY: - case TEMPERATURE_TO_TEMPERATURE: - default: - return; - } + controller->endpoint = endpoint; + controller->callback = &emberAfPluginColorControlServerXyTransitionEventHandler; + + return controller; } -static uint8_t addHue(uint8_t hue1, uint8_t hue2) +bool ColorControlServer::moveToColorCommand(uint16_t colorX, uint16_t colorY, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) { - uint16_t hue16; + EndpointId endpoint = emberAfCurrentEndpoint(); + Color16uTransitionState * colorXTransitionState = getXTransitionState(endpoint); + Color16uTransitionState * colorYTransitionState = getYTransitionState(endpoint); - hue16 = ((uint16_t) hue1); - hue16 = static_cast(hue16 + static_cast(hue2)); + if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) + { + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; + } - if (hue16 > MAX_HUE_VALUE) + if (transitionTime == 0) { - hue16 = static_cast(hue16 - MAX_HUE_VALUE - 1); + transitionTime++; } - return ((uint8_t) hue16); -} + // New command. Need to stop any active transitions. + stopAllColorTransitions(endpoint); -static uint8_t subtractHue(uint8_t hue1, uint8_t hue2) -{ - uint16_t hue16; + // Handle color mode transition, if necessary. + handleModeSwitch(endpoint, COLOR_MODE_CIE_XY); - hue16 = ((uint16_t) hue1); - if (hue2 > hue1) - { - hue16 = static_cast(hue16 + MAX_HUE_VALUE + 1); - } + // now, kick off the state machine. + ColorControl::Attributes::GetCurrentX(endpoint, &(colorXTransitionState->initialValue)); + ColorControl::Attributes::GetCurrentX(endpoint, &(colorXTransitionState->currentValue)); + colorXTransitionState->finalValue = colorX; + colorXTransitionState->stepsRemaining = transitionTime; + colorXTransitionState->stepsTotal = transitionTime; + colorXTransitionState->endpoint = endpoint; + colorXTransitionState->lowLimit = MIN_CIE_XY_VALUE; + colorXTransitionState->highLimit = MAX_CIE_XY_VALUE; + + ColorControl::Attributes::GetCurrentY(endpoint, &(colorYTransitionState->initialValue)); + ColorControl::Attributes::GetCurrentY(endpoint, &(colorYTransitionState->currentValue)); + colorYTransitionState->finalValue = colorY; + colorYTransitionState->stepsRemaining = transitionTime; + colorYTransitionState->stepsTotal = transitionTime; + colorYTransitionState->endpoint = endpoint; + colorYTransitionState->lowLimit = MIN_CIE_XY_VALUE; + colorYTransitionState->highLimit = MAX_CIE_XY_VALUE; + + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTime); - hue16 = static_cast(hue16 - static_cast(hue2)); + // kick off the state machine: + emberEventControlSetDelayMS(configureXYEventControl(endpoint), UPDATE_TIME_MS); - return ((uint8_t) hue16); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } -static bool computeNewHueValue(ColorHueTransitionState * p) +bool ColorControlServer::moveColorCommand(int16_t rateX, int16_t rateY, uint8_t optionsMask, uint8_t optionsOverride) { - uint32_t newHue32; - uint16_t newHue; + EndpointId endpoint = emberAfCurrentEndpoint(); + Color16uTransitionState * colorXTransitionState = getXTransitionState(endpoint); + Color16uTransitionState * colorYTransitionState = getYTransitionState(endpoint); - // exit with a false if hue is not currently moving - if (p->stepsRemaining == 0) + if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { - return false; + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } - (p->stepsRemaining)--; + uint16_t transitionTimeX, transitionTimeY; + uint16_t unsignedRate; - if (p->repeat == false) + // New command. Need to stop any active transitions. + stopAllColorTransitions(endpoint); + + if (rateX == 0 && rateY == 0) { - writeRemainingTime(p->endpoint, p->stepsRemaining); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } - // are we going up or down? - if ((p->isEnhancedHue && p->finalEnhancedHue == p->currentEnhancedHue) || (!p->isEnhancedHue && p->finalHue == p->currentHue)) + // Handle color mode transition, if necessary. + handleModeSwitch(endpoint, COLOR_MODE_CIE_XY); + + // now, kick off the state machine. + ColorControl::Attributes::GetCurrentX(endpoint, &(colorXTransitionState->initialValue)); + colorXTransitionState->currentValue = colorXTransitionState->initialValue; + if (rateX > 0) { - // do nothing + colorXTransitionState->finalValue = MAX_CIE_XY_VALUE; + unsignedRate = (uint16_t) rateX; } - else if (p->up) + else { - newHue32 = static_cast(p->isEnhancedHue ? subtractEnhancedHue(p->finalEnhancedHue, p->initialEnhancedHue) - : subtractHue(p->finalHue, p->initialHue)); - newHue32 *= static_cast(p->stepsRemaining); - newHue32 /= static_cast(p->stepsTotal); + colorXTransitionState->finalValue = MIN_CIE_XY_VALUE; + unsignedRate = (uint16_t)(rateX * -1); + } + transitionTimeX = computeTransitionTimeFromStateAndRate(colorXTransitionState, unsignedRate); + colorXTransitionState->stepsRemaining = transitionTimeX; + colorXTransitionState->stepsTotal = transitionTimeX; + colorXTransitionState->endpoint = endpoint; + colorXTransitionState->lowLimit = MIN_CIE_XY_VALUE; + colorXTransitionState->highLimit = MAX_CIE_XY_VALUE; - if (p->isEnhancedHue) - { - p->currentEnhancedHue = subtractEnhancedHue(p->finalEnhancedHue, static_cast(newHue32)); - } - else - { - p->currentHue = subtractHue(p->finalHue, static_cast(newHue32)); - } + ColorControl::Attributes::GetCurrentY(endpoint, &(colorYTransitionState->initialValue)); + colorYTransitionState->currentValue = colorYTransitionState->initialValue; + if (rateY > 0) + { + colorYTransitionState->finalValue = MAX_CIE_XY_VALUE; + unsignedRate = (uint16_t) rateY; } else { - newHue32 = static_cast(p->isEnhancedHue ? subtractEnhancedHue(p->initialEnhancedHue, p->finalEnhancedHue) - : subtractHue(p->initialHue, p->finalHue)); - newHue32 *= static_cast(p->stepsRemaining); - newHue32 /= static_cast(p->stepsTotal); - - if (p->isEnhancedHue) - { - p->currentEnhancedHue = addEnhancedHue(p->finalEnhancedHue, static_cast(newHue32)); - } - else - { - p->currentHue = addHue(p->finalHue, static_cast(newHue32)); - } + colorYTransitionState->finalValue = MIN_CIE_XY_VALUE; + unsignedRate = (uint16_t)(rateY * -1); } + transitionTimeY = computeTransitionTimeFromStateAndRate(colorYTransitionState, unsignedRate); + colorYTransitionState->stepsRemaining = transitionTimeY; + colorYTransitionState->stepsTotal = transitionTimeY; + colorYTransitionState->endpoint = endpoint; + colorYTransitionState->lowLimit = MIN_CIE_XY_VALUE; + colorYTransitionState->highLimit = MAX_CIE_XY_VALUE; - if (p->stepsRemaining == 0) + if (transitionTimeX < transitionTimeY) { - if (p->repeat == false) - { - // we are performing a move to and not a move. - return true; - } - else - { - // Check if we are in a color loop. If not, we are in a moveHue - uint8_t isColorLoop = 0; - Attributes::GetColorLoopActive(p->endpoint, &isColorLoop); + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTimeX); + } + else + { + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTimeY); + } - if (isColorLoop) - { - p->currentEnhancedHue = p->initialEnhancedHue; - } - else - { - // we are performing a Hue move. Need to compute the new values for the - // next move period. - if (p->up) - { - if (p->isEnhancedHue) - { - newHue = subtractEnhancedHue(p->finalEnhancedHue, p->initialEnhancedHue); - newHue = addEnhancedHue(p->finalEnhancedHue, newHue); + // kick off the state machine: + emberEventControlSetDelayMS(configureXYEventControl(endpoint), UPDATE_TIME_MS); - p->initialEnhancedHue = p->finalEnhancedHue; - p->finalEnhancedHue = newHue; - } - else - { - newHue = subtractHue(p->finalHue, p->initialHue); - newHue = addHue(p->finalHue, static_cast(newHue)); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; +} - p->initialHue = p->finalHue; - p->finalHue = static_cast(newHue); - } - } - else - { - if (p->isEnhancedHue) - { - newHue = subtractEnhancedHue(p->initialEnhancedHue, p->finalEnhancedHue); - newHue = subtractEnhancedHue(p->finalEnhancedHue, newHue); +bool ColorControlServer::stepColorCommand(int16_t stepX, int16_t stepY, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) +{ + EndpointId endpoint = emberAfCurrentEndpoint(); + Color16uTransitionState * colorXTransitionState = getXTransitionState(endpoint); + Color16uTransitionState * colorYTransitionState = getYTransitionState(endpoint); - p->initialEnhancedHue = p->finalEnhancedHue; - p->finalEnhancedHue = newHue; - } - else - { - newHue = subtractHue(p->initialHue, p->finalHue); - newHue = subtractHue(p->finalHue, static_cast(newHue)); + if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) + { + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; + } - p->initialHue = p->finalHue; - p->finalHue = static_cast(newHue); - } - } - } + uint16_t currentColorX = 0; + ColorControl::Attributes::GetCurrentX(endpoint, ¤tColorX); - p->stepsRemaining = p->stepsTotal; - } + uint16_t currentColorY = 0; + ColorControl::Attributes::GetCurrentY(endpoint, ¤tColorY); + + uint16_t colorX = findNewColorValueFromStep(currentColorX, stepX); + uint16_t colorY = findNewColorValueFromStep(currentColorY, stepY); + + if (transitionTime == 0) + { + transitionTime++; } - return false; -} -static uint16_t addEnhancedHue(uint16_t hue1, uint16_t hue2) -{ - return static_cast(hue1 + hue2); -} + // New command. Need to stop any active transitions. + stopAllColorTransitions(endpoint); -static uint16_t subtractEnhancedHue(uint16_t hue1, uint16_t hue2) -{ - return static_cast(hue1 - hue2); + // Handle color mode transition, if necessary. + handleModeSwitch(endpoint, COLOR_MODE_CIE_XY); + + // now, kick off the state machine. + colorXTransitionState->initialValue = currentColorX; + colorXTransitionState->currentValue = currentColorX; + colorXTransitionState->finalValue = colorX; + colorXTransitionState->stepsRemaining = transitionTime; + colorXTransitionState->stepsTotal = transitionTime; + colorXTransitionState->endpoint = endpoint; + colorXTransitionState->lowLimit = MIN_CIE_XY_VALUE; + colorXTransitionState->highLimit = MAX_CIE_XY_VALUE; + + colorYTransitionState->initialValue = currentColorY; + colorYTransitionState->currentValue = currentColorY; + colorYTransitionState->finalValue = colorY; + colorYTransitionState->stepsRemaining = transitionTime; + colorYTransitionState->stepsTotal = transitionTime; + colorYTransitionState->endpoint = endpoint; + colorYTransitionState->lowLimit = MIN_CIE_XY_VALUE; + colorYTransitionState->highLimit = MAX_CIE_XY_VALUE; + + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTime); + + // kick off the state machine: + emberEventControlSetDelayMS(configureXYEventControl(endpoint), UPDATE_TIME_MS); + + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } -void emberAfPluginColorControlServerHueSatTransitionEventHandler(void) +/** + * @brief Update XY color after timer is finished + * + * @param endpoint + */ +void ColorControlServer::updateXYCommand(EndpointId endpoint) { - EndpointId endpoint = colorHueTransitionState.endpoint; - bool limitReached1, limitReached2; + Color16uTransitionState * colorXTransitionState = getXTransitionState(endpoint); + Color16uTransitionState * colorYTransitionState = getYTransitionState(endpoint); + bool limitReachedX, limitReachedY; - limitReached1 = computeNewHueValue(&colorHueTransitionState); - limitReached2 = computeNewColor16uValue(&colorSaturationTransitionState); + // compute new values for X and Y. + limitReachedX = computeNewColor16uValue(colorXTransitionState); - if (limitReached1 || limitReached2) - { - stopAllColorTransitions(); - } - else - { - emberEventControlSetDelayMS(&COLOR_HSV_CONTROL, UPDATE_TIME_MS); - } + limitReachedY = computeNewColor16uValue(colorYTransitionState); - if (colorHueTransitionState.isEnhancedHue) + if (limitReachedX || limitReachedY) { - writeEnhancedHue(colorHueTransitionState.endpoint, colorHueTransitionState.currentEnhancedHue); + stopAllColorTransitions(endpoint); } else { - writeHue(colorHueTransitionState.endpoint, colorHueTransitionState.currentHue); + emberEventControlSetDelayMS(configureXYEventControl(endpoint), UPDATE_TIME_MS); } - writeSaturation(colorSaturationTransitionState.endpoint, (uint8_t) colorSaturationTransitionState.currentValue); - if (colorHueTransitionState.isEnhancedHue) - { - emberAfColorControlClusterPrintln("Enhanced Hue %d Saturation %d endpoint %d", colorHueTransitionState.currentEnhancedHue, - colorSaturationTransitionState.currentValue, endpoint); - } - else - { - emberAfColorControlClusterPrintln("Hue %d Saturation %d endpoint %d", colorHueTransitionState.currentHue, - colorSaturationTransitionState.currentValue, endpoint); - } - emberAfPluginColorControlServerComputePwmFromHsvCallback(endpoint); + // update the attributes + ColorControl::Attributes::SetCurrentX(endpoint, colorXTransitionState->currentValue); + ColorControl::Attributes::SetCurrentY(endpoint, colorYTransitionState->currentValue); + + emberAfColorControlClusterPrintln("Color X %d Color Y %d", colorXTransitionState->currentValue, + colorYTransitionState->currentValue); + + computePwmFromXy(endpoint); } -// Return value of true means we need to stop. -static bool computeNewColor16uValue(Color16uTransitionState * p) +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP +/** + * @brief Get the Temp Transition State object associated to the endpoint + * + * @param endpoint + * @return Color16uTransitionState* + */ +ColorControlServer::Color16uTransitionState * ColorControlServer::getTempTransitionState(EndpointId endpoint) { - uint32_t newValue32u; + uint16_t index = emberAfFindClusterServerEndpointIndex(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID); + return &colorTempTransitionStates[index]; +} - if (p->stepsRemaining == 0) - { - return false; - } +/** + * @brief executes move to color temp logic + * + * @param aEndpoint + * @param colorTemperature + * @param transitionTime + */ +void ColorControlServer::moveToColorTemp(EndpointId aEndpoint, uint16_t colorTemperature, uint16_t transitionTime) +{ + EndpointId endpoint = emberAfCurrentEndpoint(); + Color16uTransitionState * colorTempTransitionState = getTempTransitionState(endpoint); - (p->stepsRemaining)--; + uint16_t temperatureMin = MIN_TEMPERATURE_VALUE; + ColorControl::Attributes::GetColorTempPhysicalMin(endpoint, &temperatureMin); - Attributes::SetRemainingTime(p->endpoint, p->stepsRemaining); + uint16_t temperatureMax = MAX_TEMPERATURE_VALUE; + ColorControl::Attributes::GetColorTempPhysicalMax(endpoint, &temperatureMax); - // handle sign - if (p->finalValue == p->currentValue) + if (transitionTime == 0) { - // do nothing + transitionTime++; } - else if (p->finalValue > p->initialValue) - { - newValue32u = ((uint32_t)(p->finalValue - p->initialValue)); - newValue32u *= ((uint32_t)(p->stepsRemaining)); - newValue32u /= ((uint32_t)(p->stepsTotal)); - p->currentValue = static_cast(p->finalValue - static_cast(newValue32u)); - if (static_cast(newValue32u) > p->finalValue || p->currentValue > p->highLimit) - { - p->currentValue = p->highLimit; - } - } - else - { - newValue32u = ((uint32_t)(p->initialValue - p->finalValue)); - newValue32u *= ((uint32_t)(p->stepsRemaining)); - newValue32u /= ((uint32_t)(p->stepsTotal)); - p->currentValue = static_cast(p->finalValue + static_cast(newValue32u)); + // New command. Need to stop any active transitions. + stopAllColorTransitions(endpoint); - if (p->finalValue > UINT16_MAX - static_cast(newValue32u) || p->currentValue < p->lowLimit) - { - p->currentValue = p->lowLimit; - } + // Handle color mode transition, if necessary. + handleModeSwitch(endpoint, COLOR_MODE_TEMPERATURE); + + if (colorTemperature < temperatureMin) + { + colorTemperature = temperatureMin; } - if (p->stepsRemaining == 0) + if (colorTemperature > temperatureMax) { - // we have completed our move. - return true; + colorTemperature = temperatureMax; } - return false; + // now, kick off the state machine. + ColorControl::Attributes::GetColorTemperature(endpoint, &(colorTempTransitionState->initialValue)); + ColorControl::Attributes::GetColorTemperature(endpoint, &(colorTempTransitionState->currentValue)); + + colorTempTransitionState->finalValue = colorTemperature; + colorTempTransitionState->stepsRemaining = transitionTime; + colorTempTransitionState->stepsTotal = transitionTime; + colorTempTransitionState->endpoint = endpoint; + colorTempTransitionState->lowLimit = temperatureMin; + colorTempTransitionState->highLimit = temperatureMax; + + // kick off the state machine + emberEventControlSetDelayMS(configureTempEventControl(endpoint), UPDATE_TIME_MS); } -static uint16_t computeTransitionTimeFromStateAndRate(Color16uTransitionState * p, uint16_t rate) +/** + * @brief returns Temperature coupled to level minimum + * + * @param endpoint + * @return uint16_t + */ +uint16_t ColorControlServer::getTemperatureCoupleToLevelMin(EndpointId endpoint) { - uint32_t transitionTime; - uint16_t max, min; + uint16_t colorTemperatureCoupleToLevelMin; + EmberAfStatus status; - if (rate == 0) - { - return MAX_INT16U_VALUE; - } + status = ColorControl::Attributes::GetCoupleColorTempToLevelMinMireds(endpoint, &colorTemperatureCoupleToLevelMin); - if (p->currentValue > p->finalValue) - { - max = p->currentValue; - min = p->finalValue; - } - else + if (status != EMBER_ZCL_STATUS_SUCCESS) { - max = p->finalValue; - min = p->currentValue; + // Not less than the physical min. + ColorControl::Attributes::GetColorTempPhysicalMin(endpoint, &colorTemperatureCoupleToLevelMin); } - transitionTime = max - min; - transitionTime *= 10; - transitionTime /= rate; + return colorTemperatureCoupleToLevelMin; +} - if (transitionTime > MAX_INT16U_VALUE) - { - return MAX_INT16U_VALUE; - } +/** + * @brief Configures EnventControl callback when using Temp colors + * + * @param endpoint + */ +EmberEventControl * ColorControlServer::configureTempEventControl(EndpointId endpoint) +{ + EmberEventControl * controller = getEventControl(endpoint); - return (uint16_t) transitionTime; + controller->endpoint = endpoint; + controller->callback = &emberAfPluginColorControlServerTempTransitionEventHandler; + + return controller; } -void emberAfPluginColorControlServerXyTransitionEventHandler(void) +void ColorControlServer::startUpColorTempCommand(EndpointId endpoint) { - EndpointId endpoint = colorXTransitionState.endpoint; - bool limitReachedX, limitReachedY; - - // compute new values for X and Y. - limitReachedX = computeNewColor16uValue(&colorXTransitionState); + // 07-5123-07 (i.e. ZCL 7) 5.2.2.2.1.22 StartUpColorTemperatureMireds Attribute + // The StartUpColorTemperatureMireds attribute SHALL define the desired startup color + // temperature values a lamp SHAL use when it is supplied with power and this value SHALL + // be reflected in the ColorTemperatureMireds attribute. In addition, the ColorMode and + // EnhancedColorMode attributes SHALL be set to 0x02 (color temperature). The values of + // the StartUpColorTemperatureMireds attribute are listed in the table below. + // Value Action on power up + // 0x0000-0xffef Set the ColorTemperatureMireds attribute to this value. + // 0xffff Set the ColorTemperatureMireds attribue to its previous value. - limitReachedY = computeNewColor16uValue(&colorYTransitionState); + // Initialize startUpColorTempMireds to "maintain previous value" value 0xFFFF + uint16_t startUpColorTemp = 0xFFFF; + EmberAfStatus status = ColorControl::Attributes::GetStartUpColorTemperatureMireds(endpoint, &startUpColorTemp); - if (limitReachedX || limitReachedY) - { - stopAllColorTransitions(); - } - else + if (status == EMBER_ZCL_STATUS_SUCCESS) { - emberEventControlSetDelayMS(&COLOR_XY_CONTROL, UPDATE_TIME_MS); - } + uint16_t updatedColorTemp = MAX_TEMPERATURE_VALUE; + status = ColorControl::Attributes::GetColorTemperature(endpoint, &updatedColorTemp); - // update the attributes - writeColorX(colorXTransitionState.endpoint, colorXTransitionState.currentValue); - writeColorY(colorXTransitionState.endpoint, colorYTransitionState.currentValue); + if (status == EMBER_ZCL_STATUS_SUCCESS) + { + uint16_t tempPhysicalMin = MIN_TEMPERATURE_VALUE; + ColorControl::Attributes::GetColorTempPhysicalMin(endpoint, &tempPhysicalMin); + + uint16_t tempPhysicalMax = MAX_TEMPERATURE_VALUE; + ColorControl::Attributes::GetColorTempPhysicalMax(endpoint, &tempPhysicalMax); + + if (tempPhysicalMin <= startUpColorTemp && startUpColorTemp <= tempPhysicalMax) + { + // Apply valid startup color temp value that is within physical limits of device. + // Otherwise, the startup value is outside the device's supported range, and the + // existing setting of ColorTemp attribute will be left unchanged (i.e., treated as + // if startup color temp was set to 0xFFFF). + updatedColorTemp = startUpColorTemp; + status = ColorControl::Attributes::SetColorTemperature(endpoint, updatedColorTemp); - emberAfColorControlClusterPrintln("Color X %d Color Y %d", colorXTransitionState.currentValue, - colorYTransitionState.currentValue); + if (status == EMBER_ZCL_STATUS_SUCCESS) + { + // Set ColorMode attributes to reflect ColorTemperature. + uint8_t updateColorMode = EMBER_ZCL_COLOR_MODE_COLOR_TEMPERATURE; + ColorControl::Attributes::SetColorMode(endpoint, updateColorMode); - emberAfPluginColorControlServerComputePwmFromXyCallback(endpoint); + updateColorMode = EMBER_ZCL_ENHANCED_COLOR_MODE_COLOR_TEMPERATURE; + ColorControl::Attributes::SetEnhancedColorMode(endpoint, updateColorMode); + } + } + } + } } -void emberAfPluginColorControlServerTempTransitionEventHandler(void) +/** + * @brief updates color temp when timer is finished + * + * @param endpoint + */ +void ColorControlServer::updateTempCommand(EndpointId endpoint) { - EndpointId endpoint = colorTempTransitionState.endpoint; + Color16uTransitionState * colorTempTransitionState = getTempTransitionState(endpoint); bool limitReached; - limitReached = computeNewColor16uValue(&colorTempTransitionState); + limitReached = computeNewColor16uValue(colorTempTransitionState); if (limitReached) { - stopAllColorTransitions(); + stopAllColorTransitions(endpoint); } else { - emberEventControlSetDelayMS(&COLOR_TEMP_CONTROL, UPDATE_TIME_MS); + emberEventControlSetDelayMS(configureTempEventControl(endpoint), UPDATE_TIME_MS); } - Attributes::SetColorTemperature(colorTempTransitionState.endpoint, colorTempTransitionState.currentValue); + ColorControl::Attributes::SetColorTemperature(endpoint, colorTempTransitionState->currentValue); - emberAfColorControlClusterPrintln("Color Temperature %d", colorTempTransitionState.currentValue); + emberAfColorControlClusterPrintln("Color Temperature %d", colorTempTransitionState->currentValue); - emberAfPluginColorControlServerComputePwmFromTempCallback(endpoint); + computePwmFromTemp(endpoint); } -static bool shouldExecuteIfOff(EndpointId endpoint, uint8_t optionMask, uint8_t optionOverride) +/** + * @brief move color temp command + * + * @param moveMode + * @param rate + * @param colorTemperatureMinimum + * @param colorTemperatureMaximum + * @param optionsMask + * @param optionsOverride + * @return true + * @return false + */ +bool ColorControlServer::moveColorTempCommand(uint8_t moveMode, uint16_t rate, uint16_t colorTemperatureMinimum, + uint16_t colorTemperatureMaximum, uint8_t optionsMask, uint8_t optionsOverride) { - // From 5.2.2.2.1.10 of ZCL7 document 14-0129-15f-zcl-ch-5-lighting.docx: - // "Command execution SHALL NOT continue beyond the Options processing if - // all of these criteria are true: - // - 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." + EndpointId endpoint = emberAfCurrentEndpoint(); + Color16uTransitionState * colorTempTransitionState = getTempTransitionState(endpoint); - if (!emberAfContainsServer(endpoint, ZCL_ON_OFF_CLUSTER_ID)) + if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; } - uint8_t options; - EmberAfStatus status = - emberAfReadServerAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_OPTIONS_ATTRIBUTE_ID, &options, sizeof(options)); - if (status != EMBER_ZCL_STATUS_SUCCESS) + uint16_t tempPhysicalMin = MIN_TEMPERATURE_VALUE; + ColorControl::Attributes::GetColorTempPhysicalMin(endpoint, &tempPhysicalMin); + + uint16_t tempPhysicalMax = MAX_TEMPERATURE_VALUE; + ColorControl::Attributes::GetColorTempPhysicalMax(endpoint, &tempPhysicalMax); + + uint16_t transitionTime; + + // New command. Need to stop any active transitions. + stopAllColorTransitions(endpoint); + + if (moveMode == MOVE_MODE_STOP) { - emberAfColorControlClusterPrintln("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; + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } - bool on; - status = emberAfReadServerAttribute(endpoint, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, reinterpret_cast(&on), - sizeof(on)); - if (status != EMBER_ZCL_STATUS_SUCCESS) + if (rate == 0) { - emberAfColorControlClusterPrintln("Unable to read OnOff attribute: 0x%X", status); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_FIELD); return true; } - // The device is on - hence ExecuteIfOff does not matter - if (on) + + if (colorTemperatureMinimum < tempPhysicalMin) + { + colorTemperatureMinimum = tempPhysicalMin; + } + if (colorTemperatureMaximum > tempPhysicalMax) + { + colorTemperatureMaximum = tempPhysicalMax; + } + + // Handle color mode transition, if necessary. + handleModeSwitch(endpoint, COLOR_MODE_TEMPERATURE); + + // now, kick off the state machine. + colorTempTransitionState->initialValue = 0; + ColorControl::Attributes::GetColorTemperature(endpoint, &colorTempTransitionState->initialValue); + colorTempTransitionState->currentValue = colorTempTransitionState->initialValue; + + if (moveMode == MOVE_MODE_UP) + { + if (tempPhysicalMax > colorTemperatureMaximum) + { + colorTempTransitionState->finalValue = colorTemperatureMaximum; + } + else + { + colorTempTransitionState->finalValue = tempPhysicalMax; + } + } + else + { + if (tempPhysicalMin < colorTemperatureMinimum) + { + colorTempTransitionState->finalValue = colorTemperatureMinimum; + } + else + { + colorTempTransitionState->finalValue = tempPhysicalMin; + } + } + transitionTime = computeTransitionTimeFromStateAndRate(colorTempTransitionState, rate); + colorTempTransitionState->stepsRemaining = transitionTime; + colorTempTransitionState->stepsTotal = transitionTime; + colorTempTransitionState->endpoint = endpoint; + colorTempTransitionState->lowLimit = colorTemperatureMinimum; + colorTempTransitionState->highLimit = colorTemperatureMaximum; + + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTime); + + // kick off the state machine: + emberEventControlSetDelayMS(configureTempEventControl(endpoint), UPDATE_TIME_MS); + + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; +} + +bool ColorControlServer::moveToColorTempCommand(uint16_t colorTemperature, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) +{ + EndpointId endpoint = emberAfCurrentEndpoint(); + + if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); 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 5.2.2.2.1.10. - // ---------- The following order is important in decision making ------- - // -----------more readable ---------- - // - if (optionMask == 0xFF && optionOverride == 0xFF) + moveToColorTemp(endpoint, colorTemperature, transitionTime); + + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; +} + +bool ColorControlServer::stepColorTempCommand(uint8_t stepMode, uint16_t stepSize, uint16_t transitionTime, + uint16_t colorTemperatureMinimum, uint16_t colorTemperatureMaximum, + uint8_t optionsMask, uint8_t optionsOverride) +{ + EndpointId endpoint = emberAfCurrentEndpoint(); + Color16uTransitionState * colorTempTransitionState = getTempTransitionState(endpoint); + + if (!shouldExecuteIfOff(endpoint, optionsMask, optionsOverride)) { - // 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_COLOR_CONTROL_OPTIONS_EXECUTE_IF_OFF); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } - // ---------- The above is to distinguish if the payload is present or not - if (READBITS(optionMask, EMBER_ZCL_COLOR_CONTROL_OPTIONS_EXECUTE_IF_OFF)) + uint16_t tempPhysicalMin = MIN_TEMPERATURE_VALUE; + ColorControl::Attributes::GetColorTempPhysicalMin(endpoint, &tempPhysicalMin); + + uint16_t tempPhysicalMax = MAX_TEMPERATURE_VALUE; + ColorControl::Attributes::GetColorTempPhysicalMax(endpoint, &tempPhysicalMax); + + if (transitionTime == 0) { - // Mask is present and set in the command payload, this indicates - // use the override as temporary option - return READBITS(optionOverride, EMBER_ZCL_COLOR_CONTROL_OPTIONS_EXECUTE_IF_OFF); + transitionTime++; } - // if we are here - use the option attribute bits - return (READBITS(options, EMBER_ZCL_COLOR_CONTROL_OPTIONS_EXECUTE_IF_OFF)); + + // New command. Need to stop any active transitions. + stopAllColorTransitions(endpoint); + + if (stepMode == MOVE_MODE_STOP) + { + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; + } + + if (colorTemperatureMinimum < tempPhysicalMin) + { + colorTemperatureMinimum = tempPhysicalMin; + } + if (colorTemperatureMaximum > tempPhysicalMax) + { + colorTemperatureMaximum = tempPhysicalMax; + } + + // Handle color mode transition, if necessary. + handleModeSwitch(endpoint, COLOR_MODE_TEMPERATURE); + + // now, kick off the state machine. + colorTempTransitionState->initialValue = 0; + ColorControl::Attributes::GetColorTemperature(endpoint, &colorTempTransitionState->initialValue); + colorTempTransitionState->currentValue = colorTempTransitionState->initialValue; + + if (stepMode == MOVE_MODE_UP) + { + uint32_t finalValue32u = static_cast(colorTempTransitionState->initialValue) + static_cast(stepSize); + if (finalValue32u > UINT16_MAX) + { + colorTempTransitionState->finalValue = UINT16_MAX; + } + else + { + colorTempTransitionState->finalValue = static_cast(finalValue32u); + } + } + else + { + uint32_t finalValue32u = static_cast(colorTempTransitionState->initialValue) - static_cast(stepSize); + if (finalValue32u > UINT16_MAX) + { + colorTempTransitionState->finalValue = 0; + } + else + { + colorTempTransitionState->finalValue = static_cast(finalValue32u); + } + } + colorTempTransitionState->stepsRemaining = transitionTime; + colorTempTransitionState->stepsTotal = transitionTime; + colorTempTransitionState->endpoint = endpoint; + colorTempTransitionState->lowLimit = colorTemperatureMinimum; + colorTempTransitionState->highLimit = colorTemperatureMaximum; + + ColorControl::Attributes::SetRemainingTime(endpoint, transitionTime); + + // kick off the state machine: + emberEventControlSetDelayMS(configureTempEventControl(endpoint), UPDATE_TIME_MS); + + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + return true; } -void emberAfColorControlClusterServerInitCallback(EndpointId endpoint) +void ColorControlServer::levelControlColorTempChangeCommand(EndpointId endpoint) { -#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP - // 07-5123-07 (i.e. ZCL 7) 5.2.2.2.1.22 StartUpColorTemperatureMireds Attribute - // The StartUpColorTemperatureMireds attribute SHALL define the desired startup color - // temperature values a lamp SHAL use when it is supplied with power and this value SHALL - // be reflected in the ColorTemperatureMireds attribute. In addition, the ColorMode and - // EnhancedColorMode attributes SHALL be set to 0x02 (color temperature). The values of - // the StartUpColorTemperatureMireds attribute are listed in the table below. - // Value Action on power up - // 0x0000-0xffef Set the ColorTemperatureMireds attribute to this value. - // 0xffff Set the ColorTemperatureMireds attribue to its previous value. + // ZCL 5.2.2.1.1 Coupling color temperature to Level Control + // + // If the Level Control for Lighting cluster identifier 0x0008 is supported + // on the same endpoint as the Color Control cluster and color temperature is + // supported, it is possible to couple changes in the current level to the + // color temperature. + // + // The CoupleColorTempToLevel bit of the Options attribute of the Level + // Control cluster indicates whether the color temperature is to be linked + // with the CurrentLevel attribute in the Level Control cluster. + // + // If the CoupleColorTempToLevel bit of the Options attribute of the Level + // Control cluster is equal to 1 and the ColorMode or EnhancedColorMode + // attribute is set to 0x02 (color temperature) then a change in the + // CurrentLevel attribute SHALL affect the ColorTemperatureMireds attribute. + // This relationship is manufacturer specific, with the qualification that + // the maximum value of the CurrentLevel attribute SHALL correspond to a + // ColorTemperatureMired attribute value equal to the + // CoupleColorTempToLevelMinMireds attribute. This relationship is one-way so + // a change to the ColorTemperatureMireds attribute SHALL NOT have any effect + // on the CurrentLevel attribute. + // + // In order to simulate the behavior of an incandescent bulb, a low value of + // the CurrentLevel attribute SHALL be associated with a high value of the + // ColorTemperatureMireds attribute (i.e., a low value of color temperature + // in kelvins). + // + // If the CoupleColorTempToLevel bit of the Options attribute of the Level + // Control cluster is equal to 0, there SHALL be no link between color + // temperature and current level. - // Initialize startUpColorTempMireds to "maintain previous value" value 0xFFFF - uint16_t startUpColorTemp = 0xFFFF; - EmberAfStatus status = - emberAfReadAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_START_UP_COLOR_TEMPERATURE_MIREDS_ATTRIBUTE_ID, - CLUSTER_MASK_SERVER, reinterpret_cast(&startUpColorTemp), sizeof(startUpColorTemp), NULL); - if (status == EMBER_ZCL_STATUS_SUCCESS) + if (!emberAfContainsServer(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID)) { - uint16_t updatedColorTemp = MAX_TEMPERATURE_VALUE; - status = emberAfReadAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_COLOR_TEMPERATURE_ATTRIBUTE_ID, - CLUSTER_MASK_SERVER, reinterpret_cast(&updatedColorTemp), sizeof(updatedColorTemp), - NULL); - if (status == EMBER_ZCL_STATUS_SUCCESS) + return; + } + + uint8_t colorMode = 0; + ColorControl::Attributes::GetColorMode(endpoint, &colorMode); + + if (colorMode == COLOR_MODE_TEMPERATURE) + { + uint16_t tempCoupleMin = getTemperatureCoupleToLevelMin(endpoint); + + uint8_t currentLevel = 0x7F; + LevelControl::Attributes::GetCurrentLevel(endpoint, ¤tLevel); + + uint16_t tempPhysMax = MAX_TEMPERATURE_VALUE; + ColorControl::Attributes::GetColorTempPhysicalMax(endpoint, &tempPhysMax); + + // Scale color temp setting between the coupling min and the physical max. + // Note that mireds varies inversely with level: low level -> high mireds. + // Peg min/MAX level to MAX/min mireds, otherwise interpolate. + uint16_t newColorTemp; + if (currentLevel <= MIN_CURRENT_LEVEL) { - uint16_t tempPhysicalMin = readColorTemperatureMin(endpoint); - uint16_t tempPhysicalMax = readColorTemperatureMax(endpoint); - if (tempPhysicalMin <= startUpColorTemp && startUpColorTemp <= tempPhysicalMax) - { - // Apply valid startup color temp value that is within physical limits of device. - // Otherwise, the startup value is outside the device's supported range, and the - // existing setting of ColorTemp attribute will be left unchanged (i.e., treated as - // if startup color temp was set to 0xFFFF). - updatedColorTemp = startUpColorTemp; - status = emberAfWriteAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, - ZCL_COLOR_CONTROL_COLOR_TEMPERATURE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, - reinterpret_cast(&updatedColorTemp), ZCL_INT16U_ATTRIBUTE_TYPE); - if (status == EMBER_ZCL_STATUS_SUCCESS) - { - // Set ColorMode attributes to reflect ColorTemperature. - uint8_t updateColorMode = EMBER_ZCL_COLOR_MODE_COLOR_TEMPERATURE; - status = - emberAfWriteAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, ZCL_COLOR_CONTROL_COLOR_MODE_ATTRIBUTE_ID, - CLUSTER_MASK_SERVER, &updateColorMode, ZCL_ENUM8_ATTRIBUTE_TYPE); - updateColorMode = EMBER_ZCL_ENHANCED_COLOR_MODE_COLOR_TEMPERATURE; - status = emberAfWriteAttribute(endpoint, ZCL_COLOR_CONTROL_CLUSTER_ID, - ZCL_COLOR_CONTROL_ENHANCED_COLOR_MODE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, - &updateColorMode, ZCL_ENUM8_ATTRIBUTE_TYPE); - } - } + newColorTemp = tempPhysMax; + } + else if (currentLevel >= MAX_CURRENT_LEVEL) + { + newColorTemp = tempCoupleMin; } + else + { + uint32_t tempDelta = (((uint32_t) tempPhysMax - (uint32_t) tempCoupleMin) * currentLevel) / + (uint32_t)(MAX_CURRENT_LEVEL - MIN_CURRENT_LEVEL + 1); + newColorTemp = (uint16_t)((uint32_t) tempPhysMax - tempDelta); + } + + // Apply new color temp. + moveToColorTemp(endpoint, newColorTemp, 0); } -#endif } -void emberAfPluginColorControlServerComputePwmFromHsvCallback(EndpointId endpoint) {} +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + +/********************************************************** + * Callbacks Implementation + *********************************************************/ + +void emberAfPluginColorControlServerStopTransition(void) +{ + EndpointId endpoint = emberAfCurrentEndpoint(); + ColorControlServer::Instance().stopAllColorTransitions(endpoint); +} + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV + +bool emberAfColorControlClusterMoveHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t moveMode, + uint8_t rate, uint8_t optionsMask, uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveHueCommand(moveMode, static_cast(rate), optionsMask, optionsOverride, + false); +} + +bool emberAfColorControlClusterMoveSaturationCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint8_t moveMode, + uint8_t rate, uint8_t optionsMask, uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveSaturationCommand(moveMode, rate, optionsMask, optionsOverride); +} + +bool emberAfColorControlClusterMoveToHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t hue, + uint8_t hueMoveMode, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveToHueCommand(static_cast(hue), hueMoveMode, transitionTime, optionsMask, + optionsOverride, false); +} + +bool emberAfColorControlClusterMoveToSaturationCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint8_t saturation, + uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveToSaturationCommand(saturation, transitionTime, optionsMask, optionsOverride); +} + +bool emberAfColorControlClusterMoveToHueAndSaturationCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t hue, + uint8_t saturation, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveToHueAndSaturationCommand(static_cast(hue), saturation, transitionTime, + optionsMask, optionsOverride, false); +} + +bool emberAfColorControlClusterStepHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t stepMode, + uint8_t stepSize, uint8_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) +{ + return ColorControlServer::Instance().stepHueCommand( + stepMode, static_cast(stepSize), static_cast(transitionTime), optionsMask, optionsOverride, false); +} + +bool emberAfColorControlClusterStepSaturationCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint8_t stepMode, + uint8_t stepSize, uint8_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) +{ + return ColorControlServer::Instance().stepSaturationCommand(stepMode, stepSize, transitionTime, optionsMask, optionsOverride); +} + +bool emberAfColorControlClusterEnhancedMoveHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t moveMode, + uint16_t rate, uint8_t optionsMask, uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveHueCommand(moveMode, rate, optionsMask, optionsOverride, true); +} + +bool emberAfColorControlClusterEnhancedMoveToHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, + uint16_t enhancedHue, uint8_t direction, uint16_t transitionTime, + uint8_t optionsMask, uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveToHueCommand(enhancedHue, direction, transitionTime, optionsMask, optionsOverride, + true); +} + +bool emberAfColorControlClusterEnhancedMoveToHueAndSaturationCallback(EndpointId endpoint, app::CommandHandler * commandObj, + uint16_t enhancedHue, uint8_t saturation, + uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveToHueAndSaturationCommand(enhancedHue, saturation, transitionTime, optionsMask, + optionsOverride, true); +} + +bool emberAfColorControlClusterEnhancedStepHueCallback(EndpointId endpoint, app::CommandHandler * commandObj, uint8_t stepMode, + uint16_t stepSize, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) +{ + return ColorControlServer::Instance().stepHueCommand(stepMode, stepSize, transitionTime, optionsMask, optionsOverride, true); +} + +bool emberAfColorControlClusterColorLoopSetCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint8_t updateFlags, + uint8_t action, uint8_t direction, uint16_t time, uint16_t startHue, + uint8_t optionsMask, uint8_t optionsOverride) +{ + return ColorControlServer::Instance().colorLoopCommand(updateFlags, action, direction, time, startHue, optionsMask, + optionsOverride); +} + +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + +bool emberAfColorControlClusterMoveToColorCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint16_t colorX, + uint16_t colorY, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveToColorCommand(colorX, colorY, transitionTime, optionsMask, optionsOverride); +} + +bool emberAfColorControlClusterMoveColorCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, int16_t rateX, + int16_t rateY, uint8_t optionsMask, uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveColorCommand(rateX, rateY, optionsMask, optionsOverride); +} -void emberAfPluginColorControlServerComputePwmFromTempCallback(EndpointId endpoint) {} +bool emberAfColorControlClusterStepColorCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, int16_t stepX, + int16_t stepY, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride) +{ + return ColorControlServer::Instance().stepColorCommand(stepX, stepY, transitionTime, optionsMask, optionsOverride); +} + +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + +bool emberAfColorControlClusterMoveToColorTemperatureCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, + uint16_t colorTemperature, uint16_t transitionTime, + uint8_t optionsMask, uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveToColorTempCommand(colorTemperature, transitionTime, optionsMask, optionsOverride); +} -void emberAfPluginColorControlServerComputePwmFromXyCallback(EndpointId endpoint) {} +bool emberAfColorControlClusterMoveColorTemperatureCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, + uint8_t moveMode, uint16_t rate, uint16_t colorTemperatureMinimum, + uint16_t colorTemperatureMaximum, uint8_t optionsMask, + uint8_t optionsOverride) +{ + return ColorControlServer::Instance().moveColorTempCommand(moveMode, rate, colorTemperatureMinimum, colorTemperatureMaximum, + optionsMask, optionsOverride); +} + +bool emberAfColorControlClusterStepColorTemperatureCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, + uint8_t stepMode, uint16_t stepSize, uint16_t transitionTime, + uint16_t colorTemperatureMinimum, uint16_t colorTemperatureMaximum, + uint8_t optionsMask, uint8_t optionsOverride) +{ + return ColorControlServer::Instance().stepColorTempCommand(stepMode, stepSize, transitionTime, colorTemperatureMinimum, + colorTemperatureMaximum, optionsMask, optionsOverride); +} + +void emberAfPluginLevelControlCoupledColorTempChangeCallback(EndpointId endpoint) +{ + ColorControlServer::Instance().levelControlColorTempChangeCommand(endpoint); +} + +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + +bool emberAfColorControlClusterStopMoveStepCallback(EndpointId aEndpoint, app::CommandHandler * commandObj, uint8_t optionsMask, + uint8_t optionsOverride) +{ + return ColorControlServer::Instance().stopMoveStepCommand(optionsMask, optionsOverride); +} + +void emberAfColorControlClusterServerInitCallback(EndpointId endpoint) +{ +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + ColorControlServer::Instance().startUpColorTempCommand(endpoint); +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP +} + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP +/** + * @brief Callback for temperature update when timer is finished + * + * @param endpoint + */ +void emberAfPluginColorControlServerTempTransitionEventHandler(EndpointId endpoint) +{ + ColorControlServer::Instance().updateTempCommand(endpoint); +} +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY +/** + * @brief Callback for color update when timer is finished + * + * @param endpoint + */ +void emberAfPluginColorControlServerXyTransitionEventHandler(EndpointId endpoint) +{ + ColorControlServer::Instance().updateXYCommand(endpoint); +} +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV +/** + * @brief Callback for color hue and saturation update when timer is finished + * + * @param endpoint + */ +void emberAfPluginColorControlServerHueSatTransitionEventHandler(EndpointId endpoint) +{ + ColorControlServer::Instance().updateHueSatCommand(endpoint); +} +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV diff --git a/src/app/clusters/color-control-server/color-control-server.h b/src/app/clusters/color-control-server/color-control-server.h index 42e4bfc384e7ad..763d292c20185b 100644 --- a/src/app/clusters/color-control-server/color-control-server.h +++ b/src/app/clusters/color-control-server/color-control-server.h @@ -1,4 +1,4 @@ -/* +/** * * Copyright (c) 2020 Project CHIP Authors * @@ -15,33 +15,251 @@ * limitations under the License. */ -#pragma once - -#include - -/** @brief Compute Pwm from HSV +/** * - * This function is called from the color server when it is time for the PWMs to - * be driven with a new value from the HSV values. + * Copyright (c) 2020 Silicon Labs * - * @param endpoint The identifying endpoint Ver.: always - */ -void emberAfPluginColorControlServerComputePwmFromHsvCallback(chip::EndpointId endpoint); - -/** @brief Compute Pwm from HSV + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance the License. + * You may obtain a copy of the License at * - * This function is called from the color server when it is time for the PWMs to - * be driven with a new value from the color temperature. + * http://www.apache.org/licenses/LICENSE-2.0 * - * @param endpoint The identifying endpoint Ver.: always + * 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. */ -void emberAfPluginColorControlServerComputePwmFromTempCallback(chip::EndpointId endpoint); -/** @brief Compute Pwm from HSV - * - * This function is called from the color server when it is time for the PWMs to - * be driven with a new value from the color X and color Y values. - * - * @param endpoint The identifying endpoint Ver.: always +#pragma once + +#include +#include + +#include + +/********************************************************** + * Defines and Macros + *********************************************************/ + +#define UPDATE_TIME_MS 100 +#define TRANSITION_TIME_1S 10 + +#define MIN_CIE_XY_VALUE 0 +#define MAX_CIE_XY_VALUE 0xfeff // this value comes directly from the ZCL specification table 5.3 + +#define MIN_TEMPERATURE_VALUE 0 +#define MAX_TEMPERATURE_VALUE 0xfeff + +#define MIN_HUE_VALUE 0 +#define MAX_HUE_VALUE 254 + +#define MIN_SATURATION_VALUE 0 +#define MAX_SATURATION_VALUE 254 + +#define HALF_MAX_UINT8T 127 +#define HALF_MAX_UINT16T 0x7FFF + +#define MAX_ENHANCED_HUE_VALUE 0xFFFF + +#define MIN_CURRENT_LEVEL 0x01 +#define MAX_CURRENT_LEVEL 0xFE + +#define REPORT_FAILED 0xFF + +/** + * @brief color-control-server class */ -void emberAfPluginColorControlServerComputePwmFromXyCallback(chip::EndpointId endpoint); +class ColorControlServer +{ +public: + /********************************************************** + * Enums + *********************************************************/ + + enum MoveMode + { + MOVE_MODE_STOP = 0x00, + MOVE_MODE_UP = 0x01, + MOVE_MODE_DOWN = 0x03 + }; + + enum ColorMode + { + COLOR_MODE_HSV = 0x00, + COLOR_MODE_CIE_XY = 0x01, + COLOR_MODE_TEMPERATURE = 0x02 + }; + + enum Conversion + { + HSV_TO_HSV = 0x00, + HSV_TO_CIE_XY = 0x01, + HSV_TO_TEMPERATURE = 0x02, + CIE_XY_TO_HSV = 0x10, + CIE_XY_TO_CIE_XY = 0x11, + CIE_XY_TO_TEMPERATURE = 0x12, + TEMPERATURE_TO_HSV = 0x20, + TEMPERATURE_TO_CIE_XY = 0x21, + TEMPERATURE_TO_TEMPERATURE = 0x22 + }; + + /********************************************************** + * Structures + *********************************************************/ + + struct ColorHueTransitionState + { + uint8_t initialHue; + uint8_t currentHue; + uint8_t finalHue; + uint16_t stepsRemaining; + uint16_t stepsTotal; + uint16_t initialEnhancedHue; + uint16_t currentEnhancedHue; + uint16_t finalEnhancedHue; + chip::EndpointId endpoint; + bool up; + bool repeat; + bool isEnhancedHue; + }; + + struct Color16uTransitionState + { + uint16_t initialValue; + uint16_t currentValue; + uint16_t finalValue; + uint16_t stepsRemaining; + uint16_t stepsTotal; + uint16_t lowLimit; + uint16_t highLimit; + chip::EndpointId endpoint; + }; + + /********************************************************** + * Functions Definitions + *********************************************************/ + static ColorControlServer & Instance(); + void stopAllColorTransitions(chip::EndpointId endpoint); + bool stopMoveStepCommand(uint8_t optionsMask, uint8_t optionsOverride); + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV + bool moveHueCommand(uint8_t moveMode, uint16_t rate, uint8_t optionsMask, uint8_t optionsOverride, bool isEnhanced); + bool moveToHueCommand(uint16_t hue, uint8_t hueMoveMode, uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride, + bool isEnhanced); + bool moveToHueAndSaturationCommand(uint16_t hue, uint8_t saturation, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride, bool isEnhanced); + bool stepHueCommand(uint8_t stepMode, uint16_t stepSize, uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride, + bool isEnhanced); + bool moveSaturationCommand(uint8_t moveMode, uint8_t rate, uint8_t optionsMask, uint8_t optionsOverride); + bool moveToSaturationCommand(uint8_t saturation, uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride); + bool stepSaturationCommand(uint8_t stepMode, uint8_t stepSize, uint8_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride); + bool colorLoopCommand(uint8_t updateFlags, uint8_t action, uint8_t direction, uint16_t time, uint16_t startHue, + uint8_t optionsMask, uint8_t optionsOverride); + void updateHueSatCommand(chip::EndpointId endpoint); +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + bool moveToColorCommand(uint16_t colorX, uint16_t colorY, uint16_t transitionTime, uint8_t optionsMask, + uint8_t optionsOverride); + bool moveColorCommand(int16_t rateX, int16_t rateY, uint8_t optionsMask, uint8_t optionsOverride); + bool stepColorCommand(int16_t stepX, int16_t stepY, uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride); + void updateXYCommand(chip::EndpointId endpoint); +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + bool moveColorTempCommand(uint8_t moveMode, uint16_t rate, uint16_t colorTemperatureMinimum, uint16_t colorTemperatureMaximum, + uint8_t optionsMask, uint8_t optionsOverride); + bool moveToColorTempCommand(uint16_t colorTemperature, uint16_t transitionTime, uint8_t optionsMask, uint8_t optionsOverride); + bool stepColorTempCommand(uint8_t stepMode, uint16_t stepSize, uint16_t transitionTime, uint16_t colorTemperatureMinimum, + uint16_t colorTemperatureMaximum, uint8_t optionsMask, uint8_t optionsOverride); + void levelControlColorTempChangeCommand(chip::EndpointId endpoint); + void startUpColorTempCommand(chip::EndpointId endpoint); + void updateTempCommand(chip::EndpointId endpoint); +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + +private: + /********************************************************** + * Functions Definitions + *********************************************************/ + + ColorControlServer() {} + bool shouldExecuteIfOff(chip::EndpointId endpoint, uint8_t optionMask, uint8_t optionOverride); + void handleModeSwitch(chip::EndpointId endpoint, uint8_t newColorMode); + uint16_t computeTransitionTimeFromStateAndRate(Color16uTransitionState * p, uint16_t rate); + EmberEventControl * getEventControl(chip::EndpointId endpoint); + void computePwmFromHsv(chip::EndpointId endpoint); + void computePwmFromTemp(chip::EndpointId endpoint); + void computePwmFromXy(chip::EndpointId endpoint); + bool computeNewColor16uValue(Color16uTransitionState * p); + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV + ColorHueTransitionState * getColorHueTransitionState(chip::EndpointId endpoint); + Color16uTransitionState * getSaturationTransitionState(chip::EndpointId endpoint); + uint8_t getSaturation(chip::EndpointId endpoint); + uint8_t addHue(uint8_t hue1, uint8_t hue2); + uint8_t subtractHue(uint8_t hue1, uint8_t hue2); + uint8_t addSaturation(uint8_t saturation1, uint8_t saturation2); + uint8_t subtractSaturation(uint8_t saturation1, uint8_t saturation2); + uint16_t addEnhancedHue(uint16_t hue1, uint16_t hue2); + uint16_t subtractEnhancedHue(uint16_t hue1, uint16_t hue2); + void startColorLoop(chip::EndpointId endpoint, uint8_t startFromStartHue); + void initHueSat(chip::EndpointId endpoint, ColorHueTransitionState * colorHueTransitionState, + Color16uTransitionState * colorSatTransitionState); + bool computeNewHueValue(ColorHueTransitionState * p); + EmberEventControl * configureHSVEventControl(chip::EndpointId); +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + Color16uTransitionState * getXTransitionState(chip::EndpointId endpoint); + Color16uTransitionState * getYTransitionState(chip::EndpointId endpoint); + uint16_t findNewColorValueFromStep(uint16_t oldValue, int16_t step); + EmberEventControl * configureXYEventControl(chip::EndpointId); +#endif // #ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + Color16uTransitionState * getTempTransitionState(chip::EndpointId endpoint); + void moveToColorTemp(chip::EndpointId aEndpoint, uint16_t colorTemperature, uint16_t transitionTime); + uint16_t getTemperatureCoupleToLevelMin(chip::EndpointId endpoint); + EmberEventControl * configureTempEventControl(chip::EndpointId); +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + + /********************************************************** + * Attributes Decleration + *********************************************************/ + static ColorControlServer instance; + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV + ColorHueTransitionState colorHueTransitionStates[EMBER_AF_COLOR_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT]; + Color16uTransitionState colorSatTransitionStates[EMBER_AF_COLOR_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT]; +#endif + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + Color16uTransitionState colorXtransitionStates[EMBER_AF_COLOR_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT]; + Color16uTransitionState colorYtransitionStates[EMBER_AF_COLOR_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT]; +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + Color16uTransitionState colorTempTransitionStates[EMBER_AF_COLOR_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT]; +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + + EmberEventControl eventControls[EMBER_AF_COLOR_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT]; +}; + +/********************************************************** + * Callbacks + *********************************************************/ + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP +void emberAfPluginColorControlServerTempTransitionEventHandler(chip::EndpointId endpoint); +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_TEMP + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY +void emberAfPluginColorControlServerXyTransitionEventHandler(chip::EndpointId endpoint); +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_XY + +#ifdef EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV +void emberAfPluginColorControlServerHueSatTransitionEventHandler(chip::EndpointId endpoint); +#endif // EMBER_AF_PLUGIN_COLOR_CONTROL_SERVER_HSV diff --git a/src/app/util/af-event.cpp b/src/app/util/af-event.cpp index adf8d52e61f747..4e779641fa0e29 100644 --- a/src/app/util/af-event.cpp +++ b/src/app/util/af-event.cpp @@ -101,14 +101,11 @@ void EventControlHandler(chip::System::Layer * systemLayer, void * appState) EmberEventControl * control = reinterpret_cast(appState); if (control->status != EMBER_EVENT_INACTIVE) { - for (auto & event : emAfEvents) + control->status = EMBER_EVENT_INACTIVE; + + if (control->callback != NULL) { - if (event.control == control) - { - control->status = EMBER_EVENT_INACTIVE; - event.handler(); - return; - } + (control->callback)(control->endpoint); } } } diff --git a/src/app/util/types_stub.h b/src/app/util/types_stub.h index f52d157e582fdc..6410c6c032e8f9 100644 --- a/src/app/util/types_stub.h +++ b/src/app/util/types_stub.h @@ -1631,22 +1631,25 @@ enum EMBER_APPLICATION_ERROR_15 = 0xFF, }; +/** + * @brief Function pointer for timer callback + */ +typedef void (*TimerCallback)(chip::EndpointId); + /** @brief The control structure for events. * - * This structure should not be accessed directly. * It holds the event status (one of the @e EMBER_EVENT_ values) - * and the time left before the event fires. + * and the callback and it's parameters */ typedef struct { /** The event's status, either inactive or the units for timeToExecute. */ EmberEventUnits status; - /** The ID of the task this event belongs to. */ - EmberTaskId taskid; - /** Indicates how long before the event fires. - * Units are milliseconds. - */ - uint32_t timeToExecute; + + /* Callback information */ + TimerCallback callback; + chip::EndpointId endpoint; + } EmberEventControl; /** diff --git a/zzz_generated/all-clusters-app/zap-generated/af-gen-event.h b/zzz_generated/all-clusters-app/zap-generated/af-gen-event.h index 8c1cc0513cdd79..a7311ac7df82bd 100644 --- a/zzz_generated/all-clusters-app/zap-generated/af-gen-event.h +++ b/zzz_generated/all-clusters-app/zap-generated/af-gen-event.h @@ -45,16 +45,10 @@ EmberEventControl emberAfLevelControlClusterServerTickCallbackControl1; \ EmberEventControl emberAfBarrierControlClusterServerTickCallbackControl1; \ EmberEventControl emberAfIasZoneClusterServerTickCallbackControl1; \ - extern EmberEventControl emberAfPluginColorControlServerHueSatTransitionEventControl; \ - extern EmberEventControl emberAfPluginColorControlServerTempTransitionEventControl; \ - extern EmberEventControl emberAfPluginColorControlServerXyTransitionEventControl; \ extern EmberEventControl emberAfPluginDoorLockServerLockoutEventControl; \ extern EmberEventControl emberAfPluginDoorLockServerRelockEventControl; \ extern EmberEventControl emberAfPluginIasZoneServerManageQueueEventControl; \ extern EmberEventControl emberAfPluginReportingTickEventControl; \ - extern void emberAfPluginColorControlServerHueSatTransitionEventHandler(void); \ - extern void emberAfPluginColorControlServerTempTransitionEventHandler(void); \ - extern void emberAfPluginColorControlServerXyTransitionEventHandler(void); \ extern void emberAfPluginDoorLockServerLockoutEventHandler(void); \ extern void emberAfPluginDoorLockServerRelockEventHandler(void); \ extern void emberAfPluginIasZoneServerManageQueueEventHandler(void); \ @@ -83,7 +77,8 @@ void emberAfIasZoneClusterServerTickCallbackWrapperFunction1(void) \ { \ clusterTickWrapper(&emberAfIasZoneClusterServerTickCallbackControl1, emberAfIasZoneClusterServerTickCallback, 1); \ - } + } \ + /*to finish*/ // EmberEventData structs used to populate the EmberEventData table #define EMBER_AF_GENERATED_EVENTS \ @@ -92,10 +87,6 @@ { &emberAfBarrierControlClusterServerTickCallbackControl1, \ emberAfBarrierControlClusterServerTickCallbackWrapperFunction1 }, \ { &emberAfIasZoneClusterServerTickCallbackControl1, emberAfIasZoneClusterServerTickCallbackWrapperFunction1 }, \ - { &emberAfPluginColorControlServerHueSatTransitionEventControl, \ - emberAfPluginColorControlServerHueSatTransitionEventHandler }, \ - { &emberAfPluginColorControlServerTempTransitionEventControl, emberAfPluginColorControlServerTempTransitionEventHandler }, \ - { &emberAfPluginColorControlServerXyTransitionEventControl, emberAfPluginColorControlServerXyTransitionEventHandler }, \ { &emberAfPluginDoorLockServerLockoutEventControl, emberAfPluginDoorLockServerLockoutEventHandler }, \ { &emberAfPluginDoorLockServerRelockEventControl, emberAfPluginDoorLockServerRelockEventHandler }, \ { &emberAfPluginIasZoneServerManageQueueEventControl, emberAfPluginIasZoneServerManageQueueEventHandler }, \ @@ -103,10 +94,8 @@ #define EMBER_AF_GENERATED_EVENT_STRINGS \ "Identify Cluster Server EP 1", "Level Control Cluster Server EP 1", "Barrier Control Cluster Server EP 1", \ - "IAS Zone Cluster Server EP 1", "Color Control Cluster Server Plugin HueSatTransition", \ - "Color Control Cluster Server Plugin TempTransition", "Color Control Cluster Server Plugin XyTransition", \ - "Door Lock Server Cluster Plugin Lockout", "Door Lock Server Cluster Plugin Relock", "IAS Zone Server Plugin ManageQueue", \ - "Reporting Plugin Tick", + "IAS Zone Cluster Server EP 1", "Door Lock Server Cluster Plugin Lockout", "Door Lock Server Cluster Plugin Relock", \ + "IAS Zone Server Plugin ManageQueue", "Reporting Plugin Tick", // The length of the event context table used to track and retrieve cluster events #define EMBER_AF_EVENT_CONTEXT_LENGTH 4 diff --git a/zzz_generated/lighting-app/zap-generated/af-gen-event.h b/zzz_generated/lighting-app/zap-generated/af-gen-event.h index f0abc08b379147..6612569e6e3097 100644 --- a/zzz_generated/lighting-app/zap-generated/af-gen-event.h +++ b/zzz_generated/lighting-app/zap-generated/af-gen-event.h @@ -42,12 +42,6 @@ // Code used to configure the cluster event mechanism #define EMBER_AF_GENERATED_EVENT_CODE \ EmberEventControl emberAfLevelControlClusterServerTickCallbackControl1; \ - extern EmberEventControl emberAfPluginColorControlServerHueSatTransitionEventControl; \ - extern EmberEventControl emberAfPluginColorControlServerTempTransitionEventControl; \ - extern EmberEventControl emberAfPluginColorControlServerXyTransitionEventControl; \ - extern void emberAfPluginColorControlServerHueSatTransitionEventHandler(void); \ - extern void emberAfPluginColorControlServerTempTransitionEventHandler(void); \ - extern void emberAfPluginColorControlServerXyTransitionEventHandler(void); \ static void clusterTickWrapper(EmberEventControl * control, EmberAfTickFunction callback, uint8_t endpoint) \ { \ /* emberAfPushEndpointNetworkIndex(endpoint); */ \ @@ -55,7 +49,6 @@ (*callback)(endpoint); \ /* emberAfPopNetworkIndex(); */ \ } \ - \ void emberAfLevelControlClusterServerTickCallbackWrapperFunction1(void) \ { \ clusterTickWrapper(&emberAfLevelControlClusterServerTickCallbackControl1, emberAfLevelControlClusterServerTickCallback, \ @@ -64,15 +57,9 @@ // EmberEventData structs used to populate the EmberEventData table #define EMBER_AF_GENERATED_EVENTS \ - { &emberAfLevelControlClusterServerTickCallbackControl1, emberAfLevelControlClusterServerTickCallbackWrapperFunction1 }, \ - { &emberAfPluginColorControlServerHueSatTransitionEventControl, \ - emberAfPluginColorControlServerHueSatTransitionEventHandler }, \ - { &emberAfPluginColorControlServerTempTransitionEventControl, emberAfPluginColorControlServerTempTransitionEventHandler }, \ - { &emberAfPluginColorControlServerXyTransitionEventControl, emberAfPluginColorControlServerXyTransitionEventHandler }, + { &emberAfLevelControlClusterServerTickCallbackControl1, emberAfLevelControlClusterServerTickCallbackWrapperFunction1 }, -#define EMBER_AF_GENERATED_EVENT_STRINGS \ - "Level Control Cluster Server EP 1", "Color Control Cluster Server Plugin HueSatTransition", \ - "Color Control Cluster Server Plugin TempTransition", "Color Control Cluster Server Plugin XyTransition", +#define EMBER_AF_GENERATED_EVENT_STRINGS "Level Control Cluster Server EP 1", // The length of the event context table used to track and retrieve cluster events #define EMBER_AF_EVENT_CONTEXT_LENGTH 1