* Copyright (c) 2013-2019 dresden elektronik ingenieurtechnik gmbh.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
*/
#include <QString>
#include <QVariantMap>
#include <math.h>
#include "de_web_plugin.h"
#include "de_web_plugin_private.h"
#include "colorspace.h"
#include "device_descriptions.h"
#define MIN(A,B) (((A) <= (B)) ? (A) : (B))
#define MAX(A,B) (((A) >= (B)) ? (A) : (B))
#define MIN3(A,B,C) (((A) <= (B)) ? MIN(A,C) : MIN(B,C))
#define MAX3(A,B,C) (((A) >= (B)) ? MAX(A,C) : MAX(B,C))
\param task - the task item
\param withOnOff - if true command is send with on/off
\param upDirection - true is up, false is down
\param rate - the move rate
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskMoveLevel(TaskItem &task, bool withOnOff, bool upDirection, quint8 rate)
{
task.taskType = TaskMoveLevel;
task.req.setClusterId(LEVEL_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
if (rate > 0)
{
if (withOnOff)
{
task.zclFrame.setCommandId(0x05);
}
else
{
task.zclFrame.setCommandId(0x01);
}
}
else
{
task.zclFrame.setCommandId(0x03);
}
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
if (rate > 0)
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
quint8 direction = upDirection ? 0x00 : 0x01;
stream << direction;
stream << rate;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
* Add a OnOff task to the queue
*
* \param task - the task item
* \param cmd - command to send ONOFF_COMMAND_ON, ONOFF_COMMAND_OFF, ONOFF_COMMAND_TOGGLE or ONOFF_COMMAND_ON_WITH_TIMED_OFF
* \param ontime - ontime, used only for command ONOFF_COMMAND_ON_WITH_TIMED_OFF
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskSetOnOff(TaskItem &task, quint8 cmd, quint16 ontime, quint8 flags)
{
DBG_Assert(cmd == ONOFF_COMMAND_ON || cmd == ONOFF_COMMAND_OFF || cmd == ONOFF_COMMAND_TOGGLE || cmd == ONOFF_COMMAND_OFF_WITH_EFFECT || cmd == ONOFF_COMMAND_ON_WITH_TIMED_OFF);
if (!(cmd == ONOFF_COMMAND_ON || cmd == ONOFF_COMMAND_OFF || cmd == ONOFF_COMMAND_TOGGLE || cmd == ONOFF_COMMAND_OFF_WITH_EFFECT || cmd == ONOFF_COMMAND_ON_WITH_TIMED_OFF))
{
return false;
}
if (task.lightNode && task.lightNode->parentResource())
{
Device *device = static_cast<Device*>(task.lightNode->parentResource());
if (device && device->managed())
{
uint target = 0;
ResourceItem *onItem = task.lightNode->item(RStateOn);
const auto ddfItem = DDF_GetItem(onItem);
if (cmd == ONOFF_COMMAND_ON || cmd == ONOFF_COMMAND_ON_WITH_TIMED_OFF)
{
target = 1;
}
if (!ddfItem.writeParameters.isNull())
{
StateChange change(StateChange::StateCallFunction, SC_WriteZclAttribute, task.req.dstEndpoint());
change.addTargetValue(RStateOn, target);
task.lightNode->addStateChange(change);
return true;
}
else
{
StateChange change(StateChange::StateWaitSync, SC_SetOnOff, task.req.dstEndpoint());
change.addTargetValue(RStateOn, target);
change.addParameter(QLatin1String("cmd"), cmd);
if (cmd == ONOFF_COMMAND_ON_WITH_TIMED_OFF)
{
change.addParameter(QLatin1String("ontime"), ontime);
}
task.lightNode->addStateChange(change);
}
}
}
task.taskType = TaskSendOnOffToggle;
task.onOff = cmd == ONOFF_COMMAND_ON || cmd == ONOFF_COMMAND_ON_WITH_TIMED_OFF;
task.req.setClusterId(ONOFF_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(cmd);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
if (cmd == ONOFF_COMMAND_OFF_WITH_EFFECT)
{
const quint8 effect = 0;
const quint8 variant = 0;
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << effect;
stream << variant;
}
else if (cmd == ONOFF_COMMAND_ON_WITH_TIMED_OFF)
{
const quint16 offWaitTime = 0;
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << flags;
stream << ontime;
stream << offWaitTime;
}
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
return addTask(task);
}
* Add a brightness task to the queue
*
* \param task - the task item
* \param bri - brightness level 0..255
* \param withOnOff - also set onOff state
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskSetBrightness(TaskItem &task, uint8_t bri, bool withOnOff)
{
if (task.lightNode && task.lightNode->parentResource())
{
Device *device = static_cast<Device*>(task.lightNode->parentResource());
if (device && device->managed())
{
uint target = bri;
ResourceItem *briItem = task.lightNode->item(RStateBri);
const auto ddfItem = DDF_GetItem(briItem);
if (!ddfItem.writeParameters.isNull())
{
if (withOnOff)
{
ResourceItem *onItem = task.lightNode->item(RStateOn);
const auto ddfItem2 = DDF_GetItem(onItem);
if (!ddfItem2.writeParameters.isNull())
{
StateChange change(StateChange::StateCallFunction, SC_WriteZclAttribute, task.req.dstEndpoint());
change.addTargetValue(RStateOn, bri > 0 ? 1 : 0);
task.lightNode->addStateChange(change);
}
}
StateChange change(StateChange::StateCallFunction, SC_WriteZclAttribute, task.req.dstEndpoint());
change.addTargetValue(RStateBri, target);
task.lightNode->addStateChange(change);
return true;
}
}
}
task.taskType = TaskSetLevel;
task.level = bri;
task.onOff = withOnOff;
task.req.setClusterId(LEVEL_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
if (withOnOff)
{
task.zclFrame.setCommandId(0x04);
}
else
{
task.zclFrame.setCommandId(0x00);
}
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.level;
stream << task.transitionTime;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
* Add a color temperature increase task to the queue
*
* \param task - the task item
* \param ct - step size -65534 ..65534, 0 to stop running step
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskIncColorTemperature(TaskItem &task, int32_t ct)
{
task.taskType = TaskIncColorTemperature;
task.inc = ct;
task.req.setClusterId(COLOR_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
if (ct == 0)
{
task.zclFrame.setCommandId(0x47);
}
else
{
task.zclFrame.setCommandId(0x4C);
quint8 direction = ct > 0 ? 1 : 3;
quint16 stepSize = ct > 0 ? ct : -ct;
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << direction;
stream << stepSize;
stream << task.transitionTime;
stream << (quint16)0;
stream << (quint16)0;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
* Add a brightness increase task to the queue
*
* \param task - the task item
* \param bri - step size -254 ..254, 0 to stop running step
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskIncBrightness(TaskItem &task, int16_t bri)
{
task.taskType = TaskIncBrightness;
task.inc = bri;
task.req.setClusterId(LEVEL_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
if (bri == 0)
{
task.zclFrame.setCommandId(0x03);
}
else
{
task.zclFrame.setCommandId(0x02);
quint8 mode = bri > 0 ? 0 : 1;
quint8 stepSize = (bri > 0) ? bri : bri * -1;
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << mode;
stream << stepSize;
stream << task.transitionTime;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
* Add a set color temperature task to the queue
*
* \param task - the task item
* \param ct - mired color temperature
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskSetColorTemperature(TaskItem &task, uint16_t ct)
{
if (task.lightNode && (task.lightNode->manufacturerCode() == VENDOR_ATMEL) && (task.lightNode->modelId() == "FLS-H"))
{
float ctMin = 153;
float ctMax = 500;
float sat = (static_cast<float>(ct) - ctMin) / (ctMax - ctMin) * 254;
if (sat > 254)
{
sat = 254;
}
bool ret = addTaskSetSaturation(task, static_cast<quint8>(sat));
task.taskType = TaskSetColorTemperature;
task.colorTemperature = ct;
if (task.lightNode)
{
if (task.lightNode->toString(RStateColorMode) != QLatin1String("ct"))
{
task.lightNode->setValue(RStateColorMode, QString(QLatin1String("ct")));
}
}
return ret;
}
if (task.lightNode)
{
ResourceItem *ctMin = task.lightNode->item(RCapColorCtMin);
ResourceItem *ctMax = task.lightNode->item(RCapColorCtMax);
if (ctMin && ctMax && ctMin->toNumber() > 0 && ctMax->toNumber() > 0)
{
if (ct < ctMin->toNumber()) { ct = static_cast<quint16>(ctMin->toNumber()); }
else if (ct > ctMax->toNumber()) { ct = static_cast<quint16>(ctMax->toNumber()); }
}
if (task.lightNode->toString(RStateColorMode) != QLatin1String("ct"))
{
task.lightNode->setValue(RStateColorMode, QString(QLatin1String("ct")));
}
ResourceItem *colorCaps = task.lightNode->item(RCapColorCapabilities);
bool supportsXy = colorCaps && colorCaps->toNumber() & 0x0008;
bool supportsCt = colorCaps && colorCaps->toNumber() & 0x0010;
bool useXy = supportsXy && !supportsCt;
if (useXy)
{
quint16 x;
quint16 y;
MiredColorTemperatureToXY(ct, &x, &y);
qreal xr = x / 65535.0;
qreal yr = y / 65535.0;
if (xr < 0) { xr = 0; }
else if (xr > 1) { xr = 1; }
if (yr < 0) { yr = 0; }
else if (yr > 1) { yr = 1; }
return addTaskSetXyColor(task, xr, yr);
}
DBG_Printf(DBG_INFO, "send move to color temperature %u to 0x%016llX\n", ct, task.lightNode->address().ext());
}
task.taskType = TaskSetColorTemperature;
task.colorTemperature = ct;
task.req.setClusterId(COLOR_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x0a);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.colorTemperature;
stream << task.transitionTime;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
* Add a set enhanced hue task to the queue
*
* \param task - the task item
* \param hue - brightness level 0..65535
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskSetEnhancedHue(TaskItem &task, uint16_t hue)
{
task.taskType = TaskSetEnhancedHue;
task.hueReal = hue / (360.0 * 182.04444);
if (task.lightNode)
{
if (task.lightNode->toString(RStateColorMode) != QLatin1String("hs"))
{
task.lightNode->setValue(RStateColorMode, QString(QLatin1String("hs")));
}
}
if (task.hueReal < 0.0)
{
task.hueReal = 0.0;
}
else if (task.hueReal > 1.0)
{
task.hueReal = 1.0;
}
task.hue = static_cast<quint8>(task.hueReal * 254.0);
task.enhancedHue = hue;
task.req.setClusterId(COLOR_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x40);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
uint8_t direction = 0x00;
stream << task.enhancedHue;
stream << direction;
stream << task.transitionTime;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
* Add a set saturation task to the queue
*
* \param task - the task item
* \param sat - brightness level 0..255
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskSetSaturation(TaskItem &task, uint8_t sat)
{
task.taskType = TaskSetSat;
task.sat = sat;
if (task.lightNode)
{
if (task.lightNode->toString(RStateColorMode) != QLatin1String("hs"))
{
task.lightNode->setValue(RStateColorMode, QString(QLatin1String("hs")));
}
}
if (sat == 255)
{
sat = 254;
}
task.req.setClusterId(COLOR_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x03);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.sat;
stream << task.transitionTime;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
* Add a set hue and saturation task to the queue
*
* \param task - the task item
* \param sat - brightness level 0..254
* \param hue - hue 0..254
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskSetHueAndSaturation(TaskItem &task, uint8_t hue, uint8_t sat)
{
task.taskType = TaskSetHueAndSaturation;
task.sat = sat;
task.hue = hue;
task.hueReal = hue / 254.0;
task.enhancedHue = static_cast<quint16>(task.hueReal * 360.0 * 182.04444);
if (task.lightNode)
{
if (task.lightNode->toString(RStateColorMode) != QLatin1String("hs"))
{
task.lightNode->setValue(RStateColorMode, QString(QLatin1String("hs")));
}
}
if (sat == 255)
{
sat = 254;
}
task.req.setClusterId(COLOR_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x06);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.hue;
stream << task.sat;
stream << task.transitionTime;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
* Add a set xy color task but as hue and saturation task to the queue
*
* \param task - the task item
* \param x - normalized x coordinate 0.0 .. 1.0
* \param y - normalized y coordinate 0.0 .. 1.0
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskSetXyColorAsHueAndSaturation(TaskItem &task, double x, double y)
{
num r, g, b;
num h, s, v;
num X, Y, Z;
if (x <= 0.0) {
x = 0.00000001;
}
if (y <= 0.0) {
y = 0.00000001;
}
if (task.lightNode)
{
ResourceItem *item = task.lightNode->item(RStateBri);
if (item)
{
Y = item->toNumber() / 255.0;
}
else
{
Y = 1.0;
}
}
else {
Y = 1.0;
}
X = (Y / y) * x;
Z = (Y / y) * (1.0 - x - y);
num min = MIN3(X,Y,Z);
if (min < 0)
{
X += min;
Y += min;
Z += min;
}
num max = MAX3(X,Y,Z);
if (max > 1)
{
X /= max;
Y /= max;
Z /= max;
}
DBG_Printf(DBG_INFO, "xy = (%f, %f), XYZ = (%f, %f, %f)\n",x, y, X, Y, Z);
r = (num)( 3.2406*X - 1.5372*Y - 0.4986*Z);
g = (num)(-0.9689*X + 1.8758*Y + 0.0415*Z);
b = (num)( 0.0557*X - 0.2040*Y + 1.0570*Z);
if (r > 1) r = 1;
if (r < 0) r = 0;
if (g > 1) g = 1;
if (g < 0) g = 0;
if (b > 1) b = 1;
if (b < 0) b = 0;
Rgb2Hsv(&h, &s, &v, r, g, b);
h /= 360.0;
if (h > 1.0)
{
h = 1.0;
}
else if (h < 0.0)
{
h = 0.0;
}
uint8_t hue = h * 254.0;
uint8_t sat = s * 254.0;
return addTaskSetHueAndSaturation(task, hue, sat);
}
* Add a set xy color task to the queue
*
* \param task - the task item
* \param x - normalized x coordinate 0.0 .. 1.0
* \param y - normalized y coordinate 0.0 .. 1.0
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskSetXyColor(TaskItem &task, double x, double y)
{
task.taskType = TaskSetXyColor;
DBG_Assert(x >= 0);
DBG_Assert(x <= 1);
DBG_Assert(y >= 0);
DBG_Assert(y <= 1);
task.colorX = static_cast<quint16>(x * 65535.0);
task.colorY = static_cast<quint16>(y * 65535.0);
if (task.colorX > 65279) { task.colorX = 65279; }
else if (task.colorX == 0) { task.colorX = 1; }
if (task.colorY > 65279) { task.colorY = 65279; }
else if (task.colorY == 0) { task.colorY = 1; }
if (task.lightNode)
{
if (task.lightNode->toString(RStateColorMode) != QLatin1String("xy"))
{
task.lightNode->setValue(RStateColorMode, QString(QLatin1String("xy")));
}
if (task.lightNode->manufacturerCode() == VENDOR_ATMEL && task.lightNode->modelId() == QLatin1String("FLS-PP"))
{
task.lightNode->setValue(RStateX, task.colorX);
task.lightNode->setValue(RStateY, task.colorY);
return addTaskSetXyColorAsHueAndSaturation(task, x, y);
}
}
task.req.setClusterId(COLOR_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x07);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.colorX;
stream << task.colorY;
stream << task.transitionTime;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
* Add a set color loop task to the queue
*
* \param task - the task item
* \param colorLoopActive - wherever the color loop shall be activated
* \param speed - time in seconds for a whole color loop cycle
* \return true - on success
* false - on error
*/
bool DeRestPluginPrivate::addTaskSetColorLoop(TaskItem &task, bool colorLoopActive, uint8_t speed)
{
task.colorLoop = colorLoopActive;
task.taskType = TaskSetColorLoop;
if (task.lightNode)
{
if (!task.lightNode->supportsColorLoop())
{
return false;
}
task.lightNode->setColorLoopActive(colorLoopActive);
task.lightNode->setColorLoopSpeed(speed);
if (colorLoopActive)
{
if (task.lightNode->toString(RStateColorMode) != QLatin1String("hs"))
{
task.lightNode->setValue(RStateColorMode, QString(QLatin1String("hs")));
}
}
}
task.req.setClusterId(COLOR_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x44);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
uint8_t updateFlags = 0x07;
uint8_t action = colorLoopActive ? 0x02
: 0x00 ;
uint8_t direction = 0x01;
uint16_t time = speed;
uint16_t startHue = 0;
stream << updateFlags;
stream << action;
stream << direction;
stream << time;
stream << startHue;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
\param task - the task item
\param identifyTime - the time in seconds to identify
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskIdentify(TaskItem &task, uint16_t identifyTime)
{
task.taskType = TaskIdentify;
task.identifyTime = identifyTime;
task.req.setClusterId(IDENTIFY_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x00);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.identifyTime;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
\param task - the task item
\param effectIdentifier - the effect
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskTriggerEffect(TaskItem &task, uint8_t effectIdentifier)
{
task.taskType = TaskTriggerEffect;
task.effectIdentifier = effectIdentifier;
task.req.setClusterId(IDENTIFY_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x40);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.effectIdentifier;
stream << (uint8_t) 0x00;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
\param task - the task item
\param options - the options
\param duration - the duration
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskWarning(TaskItem &task, uint8_t options, uint16_t duration)
{
task.taskType = TaskWarning;
task.options = options;
task.duration = duration;
uint8_t strobe_duty_cycle = 10;
uint8_t strobe_level = 0;
task.req.setClusterId(IAS_WD_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x00);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.options;
stream << task.duration;
stream << strobe_duty_cycle;
stream << strobe_level;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
\param task - the task item
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskDoorLockUnlock(TaskItem &task, uint8_t cmd)
{
task.taskType = TaskDoorLock;
task.req.setClusterId(DOOR_LOCK_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(cmd);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
\param task - the task item
\param groupId - the group to which a node shall be added
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskAddToGroup(TaskItem &task, uint16_t groupId)
{
task.taskType = TaskAddToGroup;
task.groupId = groupId;
task.req.setClusterId(GROUP_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x00);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.groupId;
uint8_t cstrlen = 0;
stream << cstrlen;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
\param task - the task item
\param groupId - the group which shall be viewed
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskViewGroup(TaskItem &task, uint16_t groupId)
{
task.taskType = TaskViewGroup;
task.groupId = groupId;
task.req.setClusterId(GROUP_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x01);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.groupId;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
\param task - the task item
\param groupId - the group from which a node shall be removed
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskRemoveFromGroup(TaskItem &task, uint16_t groupId)
{
task.taskType = TaskRemoveFromGroup;
task.groupId = groupId;
task.req.setClusterId(GROUP_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x03);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << task.groupId;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}
\param task - the task item
\param groupId - the group to which the scene belongs
\param sceneId - the scene which shall be added
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskStoreScene(TaskItem &task, uint16_t groupId, uint8_t sceneId)
{
task.taskType = TaskStoreScene;
task.req.setClusterId(SCENE_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x04);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << groupId;
stream << sceneId;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
DBG_Printf(DBG_INFO, "add store scene task, aps-req-id: %u\n", task.req.id());
return addTask(task);
}
\param task - the task item
\param groupId - the group to which the scene belongs
\param sceneId - the scene which shall be added
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskAddEmptyScene(TaskItem &task, quint16 groupId, quint8 sceneId, quint16 transitionTime)
{
task.taskType = TaskAddScene;
task.groupId = groupId;
task.sceneId = sceneId;
task.transitionTime = transitionTime;
task.req.setClusterId(SCENE_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
if (transitionTime >= 10)
{
task.zclFrame.setCommandId(0x00);
transitionTime = floor(transitionTime / 10);
}
else
{
task.zclFrame.setCommandId(0x40);
}
stream << groupId;
stream << sceneId;
stream << transitionTime;
stream << (uint8_t)0x00;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
queryTime = queryTime.addSecs(2);
return addTask(task);
}
\param task - the task item
\param groupId - the group to which the scene belongs
\param sceneId - the scene which shall be added
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskAddScene(TaskItem &task, uint16_t groupId, uint8_t sceneId, const QString &lightId)
{
DBG_Assert(task.lightNode);
if (!task.lightNode)
{
return false;
}
Group *group = getGroupForId(groupId);
std::vector<Scene>::iterator i = group->scenes.begin();
std::vector<Scene>::iterator end = group->scenes.end();
for ( ;i != end; ++i)
{
if (i->id == sceneId && i->state != Scene::StateDeleted)
{
std::vector<LightState>::iterator l = i->lights().begin();
std::vector<LightState>::iterator lend = i->lights().end();
for ( ;l != lend; ++l)
{
if (l->lid() != lightId)
{
continue;
}
task.taskType = TaskAddScene;
task.req.setClusterId(SCENE_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
uint8_t on = (l->on()) ? 0x01 : 0x00;
uint16_t tt;
if (l->transitionTime() >= 10)
{
task.zclFrame.setCommandId(0x00);
tt = floor(l->transitionTime() / 10);
}
else
{
task.zclFrame.setCommandId(0x40);
tt = l->transitionTime();
}
stream << groupId;
stream << sceneId;
stream << tt;
stream << (uint8_t)0x00;
stream << (uint16_t)0x0006;
stream << (uint8_t)0x01;
stream << on;
stream << (uint16_t)0x0008;
stream << (uint8_t)0x01;
stream << l->bri();
ResourceItem *item = task.lightNode->item(RStateColorMode);
if (item &&
!task.lightNode->modelId().startsWith(QLatin1String("FLS-PP3")))
{
stream << (uint16_t)0x0300;
stream << (uint8_t)11;
if (l->colorMode() == QLatin1String("ct"))
{
quint16 x,y;
quint16 enhancedHue = 0;
ResourceItem *ctMin = task.lightNode->item(RCapColorCtMin);
ResourceItem *ctMax = task.lightNode->item(RCapColorCtMax);
if (task.lightNode->modelId().startsWith(QLatin1String("FLS-H")))
{
x = l->colorTemperature();
y = 0;
}
else if (task.lightNode->modelId().startsWith(QLatin1String("FLS-CT")))
{
x = l->colorTemperature();
y = 0;
}
else if (task.lightNode->modelId().startsWith(QLatin1String("Ribag Air O")))
{
x = l->colorTemperature();
y = 0;
}
else if (task.lightNode->modelId().startsWith(QLatin1String("ICZB-F")) ||
task.lightNode->manufacturerCode() == VENDOR_MUELLER)
{
enhancedHue = l->colorTemperature();
x = 0;
y = 0;
}
else
{
quint16 ct = l->colorTemperature();
if (ctMin && ctMax && ctMin->toNumber() > 0 && ctMax->toNumber() > 0)
{
if (ct < ctMin->toNumber()) { ct = static_cast<quint16>(ctMin->toNumber()); }
else if (ct > ctMax->toNumber()) { ct = static_cast<quint16>(ctMax->toNumber()); }
}
MiredColorTemperatureToXY(ct, &x, &y);
if (x > 65279) { x = 65279; }
else if (x == 0) { x = 1; }
if (y > 65279) { y = 65279; }
else if (y == 0) { y = 1; }
}
if (l->x() != x || l->y() != y)
{
l->setX(x);
l->setY(y);
}
stream << x;
stream << y;
stream << enhancedHue;
stream << (quint8)0;
}
else
{
stream << l->x();
stream << l->y();
stream << l->enhancedHue();
stream << l->saturation();
}
stream << (quint8)l->colorloopActive();
stream << (quint8)l->colorloopDirection();
stream << (quint16)l->colorloopTime();
}
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
queryTime = queryTime.addSecs(2);
return addTask(task);
}
return false;
}
}
return false;
}
\param task - the task item
\param groupId - the group to which the scene belongs
\param sceneId - the scene which shall be removed
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskRemoveScene(TaskItem &task, uint16_t groupId, uint8_t sceneId)
{
task.taskType = TaskStoreScene;
task.req.setClusterId(SCENE_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(0x02);
task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
{
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << groupId;
stream << sceneId;
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}