From 144eb439749c6ba99b7ec8377ccaa1331cf34dcc Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 8 Mar 2020 12:24:41 +0100 Subject: [PATCH 01/27] Update de_web_plugin.cpp - Don't replace `TaskSetLevel` tasks. New `state.on` / `state.bri` logic (see #2475) sends _Move to Level with On/Off_ for level 2 and transition time 0, followed by _On_, followed by _Move to Level_ for given brightness and transitiontime. - Fix misleading debug message when replacing task. --- de_web_plugin.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/de_web_plugin.cpp b/de_web_plugin.cpp index 8b904e90f2..4723a68747 100644 --- a/de_web_plugin.cpp +++ b/de_web_plugin.cpp @@ -10633,7 +10633,8 @@ bool DeRestPluginPrivate::addTask(const TaskItem &task) std::list::iterator i = tasks.begin(); std::list::iterator end = tasks.end(); - if ((task.taskType != TaskGetSceneMembership) && + if ((task.taskType != TaskSetLevel) && + (task.taskType != TaskGetSceneMembership) && (task.taskType != TaskGetGroupMembership) && (task.taskType != TaskGetGroupIdentifiers) && (task.taskType != TaskStoreScene) && @@ -10657,7 +10658,7 @@ bool DeRestPluginPrivate::addTask(const TaskItem &task) (i->req.asdu().size() == task.req.asdu().size())) { - DBG_Printf(DBG_INFO, "Replace task %d type %d in queue cluster 0x%04X with newer task of same type. %u runnig tasks\n", task.taskId, task.taskType, task.req.clusterId(), runningTasks.size()); + DBG_Printf(DBG_INFO, "Replace task %d type %d in queue cluster 0x%04X with newer task %d of same type. %u runnig tasks\n", i->taskId, task.taskType, task.req.clusterId(), task.taskId, runningTasks.size()); *i = task; return true; } From 632dfd4eb180ddbf5846d127184696f2117af209 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 8 Mar 2020 13:41:06 +0100 Subject: [PATCH 02/27] Update zcl_tasks.cpp Fix `addTaskSetColorLoop()`, see #2475. - Set `lightNode` attributes for colorloop only here; - Set colormode to `xy` (instead of `hs`). --- zcl_tasks.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/zcl_tasks.cpp b/zcl_tasks.cpp index d2ce19f5d8..c7d561514f 100644 --- a/zcl_tasks.cpp +++ b/zcl_tasks.cpp @@ -797,13 +797,18 @@ bool DeRestPluginPrivate::addTaskSetColorLoop(TaskItem &task, bool colorLoopActi task.colorLoop = colorLoopActive; task.taskType = TaskSetColorLoop; - if (task.lightNode && colorLoopActive) + if (task.lightNode) { - if (task.lightNode->colorMode() != QLatin1String("hs")) + task.lightNode->setColorLoopActive(colorLoopActive); + task.lightNode->setColorLoopSpeed(speed); + if (colorLoopActive) { - task.lightNode->setColorMode(QLatin1String("hs")); - Event e(RLights, RStateColorMode, task.lightNode->id()); - enqueueEvent(e); + if (task.lightNode->colorMode() != QLatin1String("xy")) + { + task.lightNode->setColorMode(QLatin1String("xy")); + Event e(RLights, RStateColorMode, task.lightNode->id()); + enqueueEvent(e); + } } } From 61eab12ae046731f405d7e695af75243d560566f Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 8 Mar 2020 14:02:17 +0100 Subject: [PATCH 03/27] Update zcl_tasks.cpp Light sets colormode to `hs` when starting the colorloop. --- zcl_tasks.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zcl_tasks.cpp b/zcl_tasks.cpp index c7d561514f..2f5226247c 100644 --- a/zcl_tasks.cpp +++ b/zcl_tasks.cpp @@ -803,9 +803,9 @@ bool DeRestPluginPrivate::addTaskSetColorLoop(TaskItem &task, bool colorLoopActi task.lightNode->setColorLoopSpeed(speed); if (colorLoopActive) { - if (task.lightNode->colorMode() != QLatin1String("xy")) + if (task.lightNode->colorMode() != QLatin1String("hs")) { - task.lightNode->setColorMode(QLatin1String("xy")); + task.lightNode->setColorMode(QLatin1String("hs")); Event e(RLights, RStateColorMode, task.lightNode->id()); enqueueEvent(e); } From d32c357e8d3b5239de1bdbd7d62a04b4a91fa7cb Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 8 Mar 2020 16:06:23 +0100 Subject: [PATCH 04/27] Update rest_lights.cpp Refactor `setLightState()`, see #2475. --- rest_lights.cpp | 1263 +++++++++++++++++++++++------------------------ 1 file changed, 615 insertions(+), 648 deletions(-) diff --git a/rest_lights.cpp b/rest_lights.cpp index 543f74cf66..dc43642946 100644 --- a/rest_lights.cpp +++ b/rest_lights.cpp @@ -485,8 +485,6 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) TaskItem taskRef; QString id = req.path[3]; taskRef.lightNode = getLightNodeForId(id); - uint hue = UINT_MAX; - uint sat = UINT_MAX; if (req.sock) { @@ -515,6 +513,8 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) taskRef.req.setDstEndpoint(taskRef.lightNode->haEndpoint().endpoint()); taskRef.req.setSrcEndpoint(getSrcEndpoint(taskRef.lightNode, taskRef.req)); taskRef.req.setDstAddressMode(deCONZ::ApsExtAddress); + taskRef.transitionTime = 4; + taskRef.onTime = 0; bool ok; QVariant var = Json::parse(req.content, ok); @@ -527,7 +527,9 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) return REQ_READY_SEND; } - if (taskRef.lightNode->type() == QLatin1String("Window covering device")) + // FIXME: use cluster instead of device type. + if (taskRef.lightNode->type() == QLatin1String("Window covering controller") || + taskRef.lightNode->type() == QLatin1String("Window covering device")) { return setWindowCoveringState(req, rsp, taskRef, map); } @@ -536,681 +538,584 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) return setWarningDeviceState(req, rsp, taskRef, map); } - // TODO: check for valid attributes in body - bool isOn = false; - bool hasOn = map.contains("on"); - bool hasBri = map.contains("bri"); - bool hasHue = map.contains("hue"); - bool hasSat = map.contains("sat"); - bool hasXy = map.contains("xy"); - bool hasCt = map.contains("ct"); - bool hasCtInc = map.contains("ct_inc"); - bool hasBriInc = map.contains("bri_inc"); - bool hasEffect = map.contains("effect"); - bool hasEffectColorLoop = false; - bool hasAlert = map.contains("alert"); - bool hasWrap = map.contains("wrap"); + static const QStringList alertList({ + "none", "select", "lselect", "blink", "breathe", "okay", "channelchange", "finish", "stop" + }); + static const QStringList effectList({ "none", "colorloop" }); - { - ResourceItem *item = taskRef.lightNode->item(RStateOn); - DBG_Assert(item != nullptr); - isOn = item ? item->toBool() : false; - } - - if (taskRef.lightNode->modelId() == QLatin1String("FLS-PP")) // old FLS-PP - { - hasXy = false; - } - - // transition time - if (map.contains("transitiontime")) - { - uint tt = map["transitiontime"].toUInt(&ok); + bool requestOk = true; + bool hasCmd = false; + bool isOn = false; + bool hasOn = false; + bool targetOn = false; + quint8 targetBri = 0xFF; + qint16 targetBriInc = -32768; + bool hasWrap = false; + bool wrap = false; + double targetX = -1.0; + double targetY = -1.0; + quint16 targetCt = 0xFFFF; + qint16 targetCtInc = -32768; + quint16 targetHue = 0xFFFF; + quint8 targetSat = 0xFF; + QString effect; + bool hasColorloopSpeed = false; + quint16 colorloopSpeed = 15; + QString alert; + quint8 targetSpeed = 0xFF; - if (ok && tt < 0xFFFFUL) - { - taskRef.transitionTime = tt; - } - } - if (map.contains("ontime")) + // Check parameters. + for (QVariantMap::const_iterator p = map.begin(); p != map.end(); p++) { - uint ot = map["ontime"].toUInt(&ok); - - if (ok && ot < 0xFFFFUL) + bool paramOk = false; + bool valueOk = false; + QString param = p.key(); + if (param == "on" && taskRef.lightNode->item(RStateOn)) { - taskRef.onTime = ot; + paramOk = true; + hasCmd = true; + if (map[param].type() == QVariant::Bool) + { + valueOk = true; + hasOn = true; + targetOn = map[param].toBool(); + } } - } - - // on/off - if (hasOn) - { - if (map["on"].type() == QVariant::Bool) + else if (param == "bri" && taskRef.lightNode->item(RStateBri)) { - isOn = map["on"].toBool(); - - if (!isOn && taskRef.lightNode->isColorLoopActive()) + paramOk = true; + hasCmd = true; + if (map[param].type() == QVariant::String && map[param].toString() == "stop") // FIXME deprecate this nonsense { - TaskItem task; - copyTaskReq(taskRef, task); - addTaskSetColorLoop(task, false, 15); - taskRef.lightNode->setColorLoopActive(false); // deactivate colorloop if active + valueOk = true; + targetBriInc = 0; } - - TaskItem task; - copyTaskReq(taskRef, task); - if (isOn && taskRef.onTime > 0 && addTaskSetOnOff(task, ONOFF_COMMAND_ON_WITH_TIMED_OFF, taskRef.onTime)) + else if (map[param].type() == QVariant::Double) { - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/on").arg(id)] = isOn; - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - taskToLocalData(task); + const int bri = map[param].toInt(&ok); + if (ok && bri >= 0 && bri <= 255) + { + valueOk = true; + targetBri = bri == 255 ? 254 : bri; + } } - else if (hasBri || - // map.contains("transitiontime") || // FIXME: use bri if transitionTime is given - addTaskSetOnOff(task, isOn ? ONOFF_COMMAND_ON : ONOFF_COMMAND_OFF, 0)) // onOff task only if no bri or transitionTime is given + } + else if (param == "bri_inc" && taskRef.lightNode->item(RStateBri)) + { + paramOk = true; + hasCmd = true; + if (map[param].type() == QVariant::Double) { - // GLEDOPTO "W" (1 channel) "GL-C-009" version 1.0.3 - // GLEDOPTO "RGB/WW/CW" (5 channel) "GL-C-008" version 1.0.3 - // do not honor "with on/off" in a "Move to level (with on/off)" command. - // Workaround by sending ONOFF_COMMAND and a LEVEL_COMMAND: - if (task.lightNode - && (task.lightNode->modelId() == QLatin1String("GL-C-008") || - task.lightNode->modelId() == QLatin1String("GL-C-009")) - && task.lightNode->swBuildId().startsWith(QLatin1String("1.0"))) + const int briInc = map[param].toInt(&ok); + if (ok && briInc >= -254 && briInc <= 254) { - if (hasBri || - // map.contains("transitiontime") || // FIXME: use bri if transitionTime is given - false) + valueOk = true; + targetBriInc = briInc; + } + } + } + else if (param == "xy" && taskRef.lightNode->item(RStateX) && taskRef.lightNode->item(RStateY) && + taskRef.lightNode->modelId() != QLatin1String("FLS-PP")) + { + // @manup: is check for FLS-PP needed, or is this already handled by check for state.xy? + paramOk = true; + hasCmd = true; + if (map[param].type() == QVariant::List) { + QVariantList xy = map["xy"].toList(); + if (xy[0].type() == QVariant::Double && xy[1].type() == QVariant::Double) { + double x = xy[0].toDouble(&ok); + double y = ok ? xy[1].toDouble(&ok) : 0; + if (ok && x >= 0.0 && x <= 1.0 && y >= 0.0 && y <= 1.0) { - // In case of turning off, the ONOFF_COMMAND should be send after the - // LEVEL_COMMAND (which is far below). Since it pretty works this way, - // it seems not worth the effort to handle the turning-of case separately. - TaskItem task2; - copyTaskReq(taskRef, task2); - addTaskSetOnOff(task2, isOn ? ONOFF_COMMAND_ON : ONOFF_COMMAND_OFF, 0); + valueOk = true; + targetX = x; + targetY = y; } } - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/on").arg(id)] = isOn; - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - taskToLocalData(task); } - else + } + else if (param == "ct" && taskRef.lightNode->item(RStateCt)) + { + paramOk = true; + hasCmd = true; + if (map[param].type() == QVariant::Double) { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + ResourceItem *item = taskRef.lightNode->item(RConfigCtMin); + const quint16 ctMin = item ? item->toNumber() : 153; + item = taskRef.lightNode->item(RConfigCtMax); + const quint16 ctMax = item ? item->toNumber() : 500; + const int ct = map[param].toInt(&ok); + if (ok && ct >= ctMin && ct <= ctMax) + { + valueOk = true; + targetCt = ct; + } } } - else + else if (param == "ct_inc" && taskRef.lightNode->item(RStateCt)) { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/on").arg(id), QString("invalid value, %1, for parameter, on").arg(map["on"].toString()))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + paramOk = true; + hasCmd = true; + if (map[param].type() == QVariant::Double) + { + ResourceItem *item = taskRef.lightNode->item(RConfigCtMin); + const quint16 ctMin = item ? item->toNumber() : 153; + item = taskRef.lightNode->item(RConfigCtMax); + const quint16 ctMax = item ? item->toNumber() : 500; + const quint16 ctRange = ctMax - ctMin; + const int ctInc = map[param].toInt(&ok); + if (ok && ctInc >= -ctRange && ctInc <= ctRange) + { + valueOk = true; + targetCtInc = ctInc; + } + } } - } - - // brightness - if (hasBri) - { - uint bri = map["bri"].toUInt(&ok); - - if (hasOn && map["on"].type() == QVariant::Bool) + else if (param == "hue" && taskRef.lightNode->item(RStateHue) && taskRef.lightNode->item(RStateSat)) { - if (!isOn) + paramOk = true; + hasCmd = true; + const int hue = map[param].toInt(&ok); + if (ok && hue >= 0 && hue <= 65535) { - bri = 0; // assume the caller wanted to switch the light off + valueOk = true; + targetHue = hue == 65535 ? 65534 : hue; } - else if (isOn && (bri == 0)) + } + else if (param == "sat" && taskRef.lightNode->item(RStateHue) && taskRef.lightNode->item(RStateSat)) + { + paramOk = true; + hasCmd = true; + const int sat = map[param].toInt(&ok); + if (ok && sat >= 0 && sat <= 255) { - bri = 1; // don't turn off light is on is true + valueOk = true; + targetSat = sat == 255 ? 254 : sat; } } - - if (!isOn && !hasOn) + else if (param == "effect" && taskRef.lightNode->item(RStateX) && taskRef.lightNode->item(RStateY)) // FIXME + // else if (param == "effect" && taskRef.lightNode->item(RStateEffect)) + { + paramOk = true; + hasCmd = true; + if (map[param].type() == QVariant::String) + { + effect = map[param].toString(); + valueOk = effectList.contains(effect); + } + } + else if (param == "colorloopspeed" && taskRef.lightNode->item(RStateX) && taskRef.lightNode->item(RStateY)) // FIXME + // else if (param == "colorloopspeed" && taskRef.lightNode->item(RStateEffect)) { - rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/bri, is not modifiable. Device is set to off.").arg(id))); + paramOk = true; + const int speed = map[param].toInt(&ok); + if (ok && speed > 0 && speed <= 255) + { + valueOk = true; + hasColorloopSpeed = true; + colorloopSpeed = speed; + } } - else if ((map["bri"].type() == QVariant::String) && map["bri"].toString() == "stop") + else if (param == "alert" && taskRef.lightNode->item(RStateAlert)) { - TaskItem task; - copyTaskReq(taskRef, task); - if (addTaskStopBrightness(task)) + paramOk = true; + hasCmd = true; + if (map[param].type() == QVariant::String) { - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/groups/%1/action/bri").arg(id)] = map["bri"]; - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - taskToLocalData(task); + alert = map[param].toString(); + valueOk = alertList.contains(alert); } - else + } + else if (param == "speed" && taskRef.lightNode->item(RStateSpeed)) + { + paramOk = true; + hasCmd = true; + if (map[param].type() == QVariant::Double) { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + const int speed = map[param].toInt(&ok); + if (ok && speed >= 0 && speed <= 6) + { + valueOk = true; + targetSpeed = speed; + } } } - else if (ok && (map["bri"].type() == QVariant::Double) && (bri < 256)) + else if (param == "transitiontime") { - TaskItem task; - copyTaskReq(taskRef, task); - if (addTaskSetBrightness(task, bri, hasOn)) + paramOk = true; + if (map[param].type() == QVariant::Double) { - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/bri").arg(id)] = map["bri"]; - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - taskToLocalData(task); + const int tt = map[param].toInt(&ok); + if (ok && tt >= 0 && tt <= 0xFFFE) + { + valueOk = true; + taskRef.transitionTime = tt; + } } - else + } + else if (param == "ontime") + { + paramOk = true; + if (map[param].type() == QVariant::Double) { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + const uint ot = map[param].toUInt(&ok); + if (ok && ot > 0 && ot < 0xFFFF) { + valueOk = true; + taskRef.onTime = ot; + } } } - else + else if (param == "wrap") { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/bri").arg(id), QString("invalid value, %1, for parameter, bri").arg(map["bri"].toString()))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + paramOk = true; + if (map[param].type() == QVariant::Bool) + { + valueOk = true; + wrap = map[param].toBool(); + } } - } - - // colorloop - if (hasEffect) - { - QString effect = map["effect"].toString(); - - if (!isOn) + if (!paramOk) { - rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/effect, is not modifiable. Device is set to off.").arg(id))); + rsp.list.append(errorToMap(ERR_PARAMETER_NOT_AVAILABLE, QString("/lights/%1/state").arg(id), QString("parameter, %1, not available").arg(param))); + requestOk = false; } - else if ((effect == "none") || (effect == "colorloop")) + else if (!valueOk) { - hasEffectColorLoop = effect == "colorloop"; - uint16_t speed = 15; + rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state").arg(id), QString("invalid value, %1, for parameter, %2").arg(map[param].toString()).arg(param))); + requestOk = false; + } + } + if (taskRef.onTime > 0 && !hasOn && alert.isEmpty()) { + rsp.list.append(errorToMap(ERR_MISSING_PARAMETER, QString("/lights/%1/state").arg(id), QString("missing parameter, on or alert, for parameter, ontime"))); + requestOk = false; + } + if (hasWrap && targetBriInc != -32768) + { + rsp.list.append(errorToMap(ERR_MISSING_PARAMETER, QString("/lights/%1/state").arg(id), QString("missing parameter, bri_inc, for parameter, wrap"))); + requestOk = false; + } + if (hasColorloopSpeed && effect.isEmpty()) + { + rsp.list.append(errorToMap(ERR_MISSING_PARAMETER, QString("/lights/%1/state").arg(id), QString("missing parameter, effect, for parameter, colorloopspeed"))); + requestOk = false; + } + if (requestOk && !hasCmd) + { + rsp.list.append(errorToMap(ERR_MISSING_PARAMETER, QString("/lights/%1/state").arg(id), QString("missing parameter to set light state"))); + requestOk = false; + } + if (!requestOk) + { + rsp.httpStatus = HttpStatusBadRequest; + return REQ_READY_SEND; + } - if (hasEffectColorLoop) - { - if (map.contains("colorloopspeed")) - { - speed = map["colorloopspeed"].toUInt(&ok); - if (ok && (map["colorloopspeed"].type() == QVariant::Double) && (speed < 256) && (speed > 0)) - { - // ok - taskRef.lightNode->setColorLoopSpeed(speed); - } - else - { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/colorloopspeed").arg(id), QString("invalid value, %1, for parameter, colorloopspeed").arg(map["colorloopspeed"].toString()))); - } - } - } + // Check whether light is on. + ResourceItem *item = taskRef.lightNode->item(RStateOn); + isOn = item ? item->toBool() : false; + // state.on: true + if (hasOn && targetOn) + { + TaskItem task; + copyTaskReq(taskRef, task); + + if (!isOn && targetBri != 0xFF) + { TaskItem task; copyTaskReq(taskRef, task); - if (addTaskSetColorLoop(task, hasEffectColorLoop, speed)) - { - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/effect").arg(id)] = map["effect"]; - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - taskToLocalData(task); - } - else - { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); - } + task.transitionTime = 0; + + addTaskSetBrightness(task, 2, true); + } + const quint8 cmd = taskRef.onTime > 0 + ? ONOFF_COMMAND_ON_WITH_TIMED_OFF + : ONOFF_COMMAND_ON; + if (addTaskSetOnOff(task, cmd, taskRef.onTime, 0)) + { + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/on").arg(id)] = true; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + taskToLocalData(task); + isOn = true; } else { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/effect").arg(id), QString("invalid value, %1, for parameter, effect").arg(map["effect"].toString()))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/on").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - // hue - if (hasHue) + // state.bri trumps state.bri_inc + if (targetBri != 0xFF) { - uint hue2 = map["hue"].toUInt(&ok); + TaskItem task; + copyTaskReq(taskRef, task); if (!isOn) { - rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/hue, is not modifiable. Device is set to off.").arg(id))); + rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, bri, is not modifiable. Device is set to off."))); } - else if (ok && (map["hue"].type() == QVariant::Double) && (hue2 <= MAX_ENHANCED_HUE)) + else if (addTaskSetBrightness(task, targetBri, false)) { - hue = hue2; - TaskItem task; - copyTaskReq(taskRef, task); - { // TODO: this is needed if saturation is set and addTaskSetEnhancedHue() will not be called - task.hueReal = (double)hue / (360.0f * 182.04444f); - - if (task.hueReal < 0.0f) - { - task.hueReal = 0.0f; - } - else if (task.hueReal > 1.0f) - { - task.hueReal = 1.0f; - } - task.hue = task.hueReal * 254.0f; - if (hue > MAX_ENHANCED_HUE_Z) - { - hue = MAX_ENHANCED_HUE_Z; - } - task.enhancedHue = hue; - task.taskType = TaskSetEnhancedHue; - } - - if (!hasXy && !hasSat) - { - ResourceItem *item = task.lightNode->item(RStateSat); - double r, g, b; - double x, y; - double h = ((360.0 / 65535.0) * hue); - double s = (item ? item->toNumber() : 0) / 255.0; - double v = 1.0; - - Hsv2Rgb(&r, &g, &b, h, s, v); - Rgb2xy(&x, &y, r, g, b); - - if (x < 0) { x = 0; } - else if (x > 1) { x = 1; } - - if (y < 0) { y = 0; } - else if (y > 1) { y = 1; } - - DBG_Printf(DBG_INFO, "x: %f, y: %f\n", x, y); - x *= 65535.0; - y *= 65535.0; - - if (x > 65279) { x = 65279; } - else if (x < 1) { x = 1; } - - if (y > 65279) { y = 65279; } - else if (y < 1) { y = 1; } - - item = task.lightNode->item(RStateX); - if (item && item->toNumber() != static_cast(x)) - { - item->setValue(static_cast(x)); - Event e(RLights, RStateX, task.lightNode->id(), item); - enqueueEvent(e); - } - - item = task.lightNode->item(RStateY); - if (item && item->toNumber() != static_cast(y)) - { - item->setValue(static_cast(y)); - Event e(RLights, RStateY, task.lightNode->id(), item); - enqueueEvent(e); - } - } + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/bri").arg(id)] = targetBri; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + taskToLocalData(task); + } + else + { + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/bri").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + } + } + else if (targetBriInc != -32768) + { + TaskItem task; + copyTaskReq(taskRef, task); - if (hasSat || // merge later to set hue and saturation - hasXy || hasCt || hasEffectColorLoop || - addTaskSetEnhancedHue(task, hue)) // will only be evaluated if no sat, xy, ct or colorloop is set + if (wrap) + { + const quint8 bri = taskRef.lightNode->item(RStateBri)->toNumber(); + if (targetBriInc < 0 && bri + targetBriInc <= -targetBriInc) { - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/hue").arg(id)] = map["hue"]; - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - taskToLocalData(task); + targetBriInc = 254; } - else + else if(targetBriInc > 0 && bri + targetBriInc >= 254) { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + targetBriInc = -254; } } + + if (!isOn) + { + rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, bri_inc, is not modifiable. Device is set to off."))); + } + else if (addTaskIncBrightness(task, targetBriInc)) + { + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/bri_inc").arg(id)] = targetBriInc; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + taskToLocalData(task); + } else { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/hue").arg(id), QString("invalid value, %1, for parameter, hue").arg(map["hue"].toString()))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/bri_inc").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - // saturation - if (hasSat) + // state.effect: "none" + if (effect == "none") { - uint sat2 = map["sat"].toUInt(&ok); + TaskItem task; + copyTaskReq(taskRef, task); - if (!isOn) + if (addTaskSetColorLoop(task, false, colorloopSpeed)) { - rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/sat, is not modifiable. Device is set to off.").arg(id))); + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/effect").arg(id)] = effect; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + taskToLocalData(task); } - else if (ok && (map["sat"].type() == QVariant::Double) && (sat2 < 256)) + else { - if (sat2 == 255) - { - sat2 = 254; // max valid value for level attribute - } - - TaskItem task; - copyTaskReq(taskRef, task); - sat = sat2; - task.sat = sat; - task.taskType = TaskSetSat; - - if (!hasXy && !hasHue) - { - ResourceItem *item = task.lightNode->item(RStateHue); - double r, g, b; - double x, y; - double h = ((360.0 / 65535.0) * (item ? item->toNumber() : 0)); - double s = sat / 255.0; - double v = 1.0; - - Hsv2Rgb(&r, &g, &b, h, s, v); - Rgb2xy(&x, &y, r, g, b); - - if (x < 0) { x = 0; } - else if (x > 1) { x = 1; } - - if (y < 0) { y = 0; } - else if (y > 1) { y = 1; } - - x *= 65535.0; - y *= 65535.0; - - if (x > 65279) { x = 65279; } - else if (x < 1) { x = 1; } - - if (y > 65279) { y = 65279; } - else if (y < 1) { y = 1; } + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/effect").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + } + } - item = task.lightNode->item(RStateX); - if (item && item->toNumber() != static_cast(x)) - { - item->setValue(static_cast(x)); - Event e(RLights, RStateX, task.lightNode->id(), item); - enqueueEvent(e); - } + // state.xy trumps state.ct trumps state.ct_inc trumps state.hue, state.sat + if (targetX != -1.0 && targetY != -1.0) + { + TaskItem task; + copyTaskReq(taskRef, task); - item = task.lightNode->item(RStateY); - if (item && item->toNumber() != static_cast(y)) - { - item->setValue(static_cast(y)); - Event e(RLights, RStateY, task.lightNode->id(), item); - enqueueEvent(e); - } - } + if (!isOn) + { + rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, xy, is not modifiable. Device is set to off."))); + } + else if (addTaskSetXyColor(task, targetX, targetY)) + { + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/xy").arg(id)] = map["xy"]; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + taskToLocalData(task); + } + else + { + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/xy").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + } + } + else if (targetCt != 0xFFFF) + { + TaskItem task; + copyTaskReq(taskRef, task); - if (hasXy || hasCt - || (!hasEffectColorLoop && hasHue && (hue != UINT_MAX)) // merge later to set hue and saturation - || addTaskSetSaturation(task, sat)) // will only be evaluated if no hue, xy, ct is set - { - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/sat").arg(id)] = map["sat"]; - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - taskToLocalData(task); - } - else - { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); - } + if (!isOn) + { + rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, ct, is not modifiable. Device is set to off."))); + } + else if (addTaskSetColorTemperature(task, targetCt)) + { + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/ct").arg(id)] = targetCt; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + taskToLocalData(task); } else { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/sat").arg(id), QString("invalid value, %1, for parameter, sat").arg(map["sat"].toString()))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/ct").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - - // ct_inc - if (hasCtInc) + else if (targetCtInc != -32768) { - ResourceItem *item = taskRef.lightNode->item(RStateCt); - - int ct_inc = map["ct_inc"].toInt(&ok); + TaskItem task; + copyTaskReq(taskRef, task); - if (!item) - { - rsp.list.append(errorToMap(ERR_PARAMETER_NOT_AVAILABLE, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/ct_inc, is not available.").arg(id))); - } - else if (!isOn) - { - rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/ct_inc, is not modifiable. Device is set to off.").arg(id))); - } - else if (hasCt) + if (!isOn) { - rsp.list.append(errorToMap(ERR_PARAMETER_NOT_MODIFIEABLE, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/ct_inc, is not modifiable. ct was specified.").arg(id))); + rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, ct_inc, is not modifiable. Device is set to off."))); } - else if (ok && (map["ct_inc"].type() == QVariant::Double) && (ct_inc >= -65534 && ct_inc <= 65534)) + else if (addTaskIncColorTemperature(task, targetCtInc)) { - TaskItem task; - copyTaskReq(taskRef, task); - task.inc = ct_inc; - task.taskType = TaskIncColorTemperature; - - if (addTaskIncColorTemperature(task, ct_inc)) // will only be evaluated if no ct is set - { - taskToLocalData(task); - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/ct").arg(id)] = item->toNumber(); - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - } - else - { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); - } + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/ct_inc").arg(id)] = targetCtInc; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + taskToLocalData(task); } else { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/ct_inc").arg(id), QString("invalid value, %1, for parameter, ct_inc").arg(map["ct_inc"].toString()))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/ct_inc").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - - if (hasBriInc && !hasBri) + else if (targetHue != 0xFFFF || targetSat != 0xFF) { - ResourceItem *item = taskRef.lightNode->item(RStateBri); - - int briInc = map["bri_inc"].toInt(&ok); - - if (ok && hasWrap && map["wrap"].type() == QVariant::Bool && map["wrap"].toBool() == true) { - const int bri = static_cast(item->toNumber()); - - if (briInc < 0 && bri + briInc <= -briInc) - { - briInc = 254; - } - else if(briInc > 0 && bri + briInc >= 254) - { - briInc = -254; - } - } + TaskItem task; + copyTaskReq(taskRef, task); - if (!item) - { - rsp.list.append(errorToMap(ERR_PARAMETER_NOT_AVAILABLE, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/bri_inc, is not available.").arg(id))); - } if (!isOn) { - rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/bri, is not modifiable. Device is set to off.").arg(id))); + rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, ct_inc, is not modifiable. Device is set to off."))); } - else if (ok && (map["bri_inc"].type() == QVariant::Double) && (briInc >= -254 && briInc <= 254)) - { - TaskItem task; - copyTaskReq(taskRef, task); - task.inc = briInc; - task.taskType = TaskIncBrightness; - if (addTaskIncBrightness(task, briInc)) - { - taskToLocalData(task); - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/bri").arg(id)] = item->toNumber(); - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - } - else - { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); - } + if (targetSat == 0xFF) // only state.hue + { + ok = addTaskSetEnhancedHue(task, targetHue); } - else + else if (targetHue == 0xFFFF) // only state.sat { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/bri_inc").arg(id), QString("invalid value, %1, for parameter, bri_inc").arg(map["bri_inc"].toString()))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + ok = addTaskSetSaturation(task, targetSat); } - } - - // hue and saturation - if (hasHue && hasSat && !hasXy && !hasCt) - { - if (!isOn) + else // both state.hue and state.sat { - // no error here + const quint8 hue = targetHue / 256; + ok = addTaskSetHueAndSaturation(task, hue, targetSat); // FIXME + // ok = addTaskSetEnhancedHueAndSaturation(task, targetHue, targetSat); } - else if (!hasEffectColorLoop && (hue != UINT_MAX) && (sat != UINT_MAX)) + if (ok) { - // need 8 bit hue - qreal f = (qreal)hue / 182.04444; - - f /= 360.0; - - if (f > 1.0) - { - f = 1.0; - } - - hue = f * 254.0; - - DBG_Printf(DBG_INFO, "hue: %u, sat: %u\n", hue, sat); + // FIXME: do we need this? + quint16 hue = targetHue == 0xFFFF ? taskRef.lightNode->item(RStateHue)->toNumber() : targetHue; + quint8 sat = targetSat == 0xFF ? taskRef.lightNode->item(RStateHue)->toNumber() : targetSat; double r, g, b; double x, y; - double h = ((360.0 / 65535.0) * hue); + double h = (hue * 360.0) / 65535.0; double s = sat / 254.0; double v = 1.0; Hsv2Rgb(&r, &g, &b, h, s, v); Rgb2xy(&x, &y, r, g, b); + if (x < 0) { x = 0; } else if (x > 1) { x = 1; } - if (y < 0) { y = 0; } else if (y > 1) { y = 1; } - TaskItem task; - copyTaskReq(taskRef, task); - DBG_Printf(DBG_INFO, "x: %f, y: %f\n", x, y); - task.lightNode->setColorXY(static_cast(x * 65535.0), static_cast(y * 65535.0)); + x *= 65535.0; + y *= 65535.0; + if (x > 65279) { x = 65279; } + else if (x < 1) { x = 1; } + if (y > 65279) { y = 65279; } + else if (y < 1) { y = 1; } - if (!addTaskSetHueAndSaturation(task, hue, sat)) + item = task.lightNode->item(RStateX); + if (item && item->toNumber() != static_cast(x)) { - DBG_Printf(DBG_INFO, "can't send task set hue and saturation\n"); + item->setValue(static_cast(x)); + Event e(RLights, RStateX, task.lightNode->id(), item); + enqueueEvent(e); } - } - else - { - DBG_Printf(DBG_INFO, "can't merge hue and saturation: invalid value(s) hue: %u, sat: %u\n", hue, sat); - } - } - - // xy - if (hasXy) - { - QVariantList ls = map["xy"].toList(); - - if (!isOn) - { - rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/xy, is not modifiable. Device is set to off.").arg(id))); - } - else if ((ls.size() == 2) && (ls[0].type() == QVariant::Double) && (ls[1].type() == QVariant::Double)) - { - double x = ls[0].toDouble(&ok); - double y = ok ? ls[1].toDouble(&ok) : 0; - TaskItem task; - copyTaskReq(taskRef, task); - - if (!ok || (x < 0) || (x > 1) || (y < 0) || (y > 1)) + item = task.lightNode->item(RStateY); + if (item && item->toNumber() != static_cast(y)) { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1").arg(id), QString("invalid value, [%1,%2], for parameter, /lights/%3/xy").arg(x).arg(y).arg(id))); + item->setValue(static_cast(y)); + Event e(RLights, RStateY, task.lightNode->id(), item); + enqueueEvent(e); } - else if (hasEffectColorLoop || - addTaskSetXyColor(task, x, y)) // will only be evaluated if no color loop is set + // End FIXME + + if (targetHue != 0xFFFF) { QVariantMap rspItem; QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/xy").arg(id)] = map["xy"]; + rspItemState[QString("/lights/%1/state/hue").arg(id)] = targetHue; rspItem["success"] = rspItemState; rsp.list.append(rspItem); - taskToLocalData(task); } - else + if (targetSat != 0xFF) { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/sat").arg(id)] = targetSat; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); } + taskToLocalData(task); } else { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/xy").arg(id), QString("invalid value, %1, for parameter, xy").arg(map["xy"].toString()))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/sat").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - // color temperature - if (hasCt) + // state.effect: "colorloop" + if (effect == "colorloop") { - uint16_t ct = map["ct"].toUInt(&ok); + TaskItem task; + copyTaskReq(taskRef, task); - if (!isOn) - { - rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1").arg(id), QString("parameter, /lights/%1/ct, is not modifiable. Device is set to off.").arg(id))); - } - else if (ok && (map["ct"].type() == QVariant::Double)) + if (addTaskSetColorLoop(task, true, colorloopSpeed)) { - TaskItem task; - copyTaskReq(taskRef, task); - if (hasXy || hasEffectColorLoop || - addTaskSetColorTemperature(task, ct)) // will only be evaluated if no xy and color loop is set - { - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/ct").arg(id)] = map["ct"]; - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - if (task.taskType == TaskSetColorTemperature) - { - taskToLocalData(task); // get through reading - } - } - else - { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); - } + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/effect").arg(id)] = effect; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + taskToLocalData(task); } else { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/ct").arg(id), QString("invalid value, %1, for parameter, ct").arg(map["ct"].toString()))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/effect").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - // alert - if (hasAlert) + // state.alert + if (!alert.isEmpty()) { TaskItem task; copyTaskReq(taskRef, task); - QString alert = map["alert"].toString(); if (alert == "none") { @@ -1257,14 +1162,6 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) task.taskType = TaskTriggerEffect; task.effectIdentifier = 0xff; } - else - { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/alert").arg(id), QString("invalid value, %1, for parameter, alert").arg(map["alert"].toString()))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; - } - - taskToLocalData(task); if ((task.taskType == TaskIdentify && addTaskIdentify(task, task.identifyTime)) || (task.taskType == TaskTriggerEffect && addTaskTriggerEffect(task, task.effectIdentifier))) @@ -1274,6 +1171,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) rspItemState[QString("/lights/%1/state/alert").arg(id)] = map["alert"]; rspItem["success"] = rspItemState; rsp.list.append(rspItem); + // Don't update write-only state.alert. } else { @@ -1281,6 +1179,62 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) } } + // state.speed + if (targetSpeed != 0xFF) + { + TaskItem task; + copyTaskReq(taskRef, task); + const quint16 cluster = FAN_CONTROL_CLUSTER_ID; + const quint16 attrId = 0x0000; // Fan Mode + const quint8 type = deCONZ::Zcl8BitEnum; + + deCONZ::ZclAttribute attr(attrId, type, "speed", deCONZ::ZclReadWrite, true); + attr.setValue((quint64) targetSpeed); + ok = writeAttribute(taskRef.lightNode, taskRef.lightNode->haEndpoint().endpoint(), cluster, attr); + if (addTask(task)) + { + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/speed").arg(id)] = map["speed"]; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + taskToLocalData(task); + } + else + { + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/speed").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + } + } + + // state.on: false + if (hasOn && !targetOn) + { + if (taskRef.lightNode->isColorLoopActive()) + { + taskRef.lightNode->setColorLoopSpeed(15); + taskRef.lightNode->setColorLoopActive(false); + } + + // send Off_with_effect(0, 0) + + TaskItem task; + copyTaskReq(taskRef, task); + + if (addTaskSetOnOff(task, ONOFF_COMMAND_OFF, 0, 0)) + { + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/on").arg(id)] = false; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + taskToLocalData(task); + } + else + { + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/on").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + } + } + if (taskRef.lightNode) { updateLightEtag(taskRef.lightNode); @@ -1298,6 +1252,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) */ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiResponse &rsp, TaskItem &taskRef, QVariantMap &map) { + bool ok; QString id = req.path[3]; quint16 cluster = WINDOW_COVERING_CLUSTER_ID; // if (taskRef.lightNode->modelId().startsWith(QLatin1String("lumi.curtain"))) // FIXME - for testing only. @@ -1306,6 +1261,8 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon cluster = ANALOG_OUTPUT_CLUSTER_ID; } + bool requestOk = true; + bool hasCmd = false; bool hasOn = false; bool hasBri = false; bool hasStop = false; @@ -1314,15 +1271,18 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon quint8 targetTiltPct = 0xFF; // Check parameters. - QVariantMap::const_iterator k = map.begin(); - QVariantMap::const_iterator kend = map.end(); - for (; k != kend; ++k) + for (QVariantMap::const_iterator p = map.begin(); p != map.end(); p++) { - QString param = k.key(); + bool paramOk = false; + bool valueOk = false; + QString param = p.key(); if (param == "on" && taskRef.lightNode->item(RStateOn)) { + paramOk = true; + hasCmd = true; if (map["on"].type() == QVariant::Bool) { + valueOk = true; if (cluster == ANALOG_OUTPUT_CLUSTER_ID) { targetLiftPct = map["on"].toBool() ? 254 : 0; @@ -1333,79 +1293,76 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon targetOn = map["on"].toBool(); } } - else - { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/%2").arg(id).arg(param), QString("invalid value, %1, for parameter, %2").arg(map[param].toString()).arg(param))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; - } } else if (param == "bri" && taskRef.lightNode->item(RStateBri)) { - bool ok = false; - + paramOk = true; + hasCmd = true; if (map[param].type() == QVariant::String && map[param].toString() == "stop" && cluster != ANALOG_OUTPUT_CLUSTER_ID) { + valueOk = true; hasStop = true; - ok = true; } else if (map[param].type() == QVariant::Double) { - const int bri = map[param].toInt(); - if (bri >= 0 && bri <= 255) + const int bri = map[param].toInt(&ok); + if (ok && bri >= 0 && bri <= 255) { + valueOk = true; hasBri = true; targetLiftPct = bri * 100 / 254; - ok = true; } } - if (!ok) - { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/%2").arg(id).arg(param), QString("invalid value, %1, for parameter, %2").arg(map[param].toString()).arg(param))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; - } } else if (param == "bri_inc" && taskRef.lightNode->item(RStateBri) && cluster != ANALOG_OUTPUT_CLUSTER_ID) { - if (map[param].type() == QVariant::Double && map[param].toInt() == 0) - { - hasStop = true; - } - else + paramOk = true; + hasCmd = true; + if (map[param].type() == QVariant::Double) { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/%2").arg(id).arg(param), QString("invalid value, %1, for parameter, %2").arg(map[param].toString()).arg(param))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + const int bri_inc = map[param].toInt(&ok); + if (ok && bri_inc == 0) + { + valueOk = true; + hasStop = true; + } } } else if (param == "sat" && taskRef.lightNode->item(RStateSat)) { - bool ok = false; - + paramOk = true; + hasCmd = true; if (map[param].type() == QVariant::Double) { - const int sat = map[param].toInt(); - if (sat >= 0 && sat <= 255) + const int sat = map[param].toInt(&ok); + if (ok && sat >= 0 && sat <= 255) { + valueOk = true; targetTiltPct = sat * 100 / 254; - ok = true; } } - if (!ok) - { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/%2").arg(id).arg(param), QString("invalid value, %1, for parameter, %2").arg(map[param].toString()).arg(param))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; - } } - else + if (!paramOk) { - rsp.list.append(errorToMap(ERR_PARAMETER_NOT_AVAILABLE, QString("/lights/%1/state/%2").arg(id).arg(param), QString("parameter, %1, not available").arg(param))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + rsp.list.append(errorToMap(ERR_PARAMETER_NOT_AVAILABLE, QString("/lights/%1/state").arg(id), QString("parameter, %1, not available").arg(param))); + requestOk = false; + } + else if (!valueOk) + { + rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state").arg(id), QString("invalid value, %1, for parameter, %2").arg(map[param].toString()).arg(param))); + requestOk = false; } } + if (requestOk && !hasCmd) + { + rsp.list.append(errorToMap(ERR_MISSING_PARAMETER, QString("/lights/%1/state").arg(id), QString("missing parameter to set window covering device state"))); + requestOk = false; + } + if (!requestOk) + { + rsp.httpStatus = HttpStatusBadRequest; + return REQ_READY_SEND; + } if (hasOn && targetOn && targetLiftPct == 0) { @@ -1444,7 +1401,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon } else { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/bri_inc").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } else if (targetLiftPct != 0xFF) @@ -1512,7 +1469,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon } else { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/bri").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } else if (hasOn) @@ -1531,7 +1488,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon } else { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/on").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } @@ -1552,7 +1509,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon } else { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/sat").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } @@ -1567,68 +1524,75 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon */ int DeRestPluginPrivate::setWarningDeviceState(const ApiRequest &req, ApiResponse &rsp, TaskItem &taskRef, QVariantMap &map) { + bool ok; QString id = req.path[3]; + + bool requestOk = false; + bool hasCmd = false; QString alert; quint16 onTime = 0; static const QStringList alertList({ "none", "select", "lselect", "blink" }); // Check parameters. - QVariantMap::const_iterator k = map.begin(); - QVariantMap::const_iterator kend = map.end(); - for (; k != kend; ++k) + for (QVariantMap::const_iterator p = map.begin(); p != map.end(); p++) { - QString param = k.key(); + bool paramOk = false; + bool valueOk = false; + QString param = p.key(); if (param == "alert" && taskRef.lightNode->item(RStateAlert)) { - bool ok = false; + paramOk = true; + hasCmd = true; if (map[param].type() == QVariant::String) { alert = map[param].toString(); - ok = alertList.contains(alert); - } - if (!ok) - { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/%2").arg(id).arg(param), QString("invalid value, %1, for parameter, %2").arg(map[param].toString()).arg(param))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + valueOk = alertList.contains(alert); } } else if (param == "ontime") { - bool ok = false; - + paramOk = true; if (map[param].type() == QVariant::Double) { const uint ot = map[param].toUInt(&ok); if (ok && ot > 0 && ot < 0xFFFF) { - onTime = ot; - } - else - { - ok = false; + valueOk = true; + taskRef.onTime = ot; } } - if (!ok) - { - rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/%2").arg(id).arg(param), QString("invalid value, %1, for parameter, %2").arg(map[param].toString()).arg(param))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; - } } - else + if (!paramOk) { - rsp.list.append(errorToMap(ERR_PARAMETER_NOT_AVAILABLE, QString("/lights/%1/state/%2").arg(id).arg(param), QString("parameter, %1, not available").arg(param))); - rsp.httpStatus = HttpStatusBadRequest; - return REQ_READY_SEND; + rsp.list.append(errorToMap(ERR_PARAMETER_NOT_AVAILABLE, QString("/lights/%1/state").arg(id).arg(param), QString("parameter, %1, not available").arg(param))); + requestOk = false; + } + else if (!valueOk) + { + rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/lights/%1/state/%2").arg(id).arg(param), QString("invalid value, %1, for parameter, %2").arg(map[param].toString()).arg(param))); + requestOk = false; } } + if (taskRef.onTime > 0 && alert.isEmpty()) { + rsp.list.append(errorToMap(ERR_MISSING_PARAMETER, QString("/lights/%1/state").arg(id), QString("missing parameter, alert, for parameter, ontime"))); + requestOk = false; + } + if (requestOk && !hasCmd) + { + rsp.list.append(errorToMap(ERR_MISSING_PARAMETER, QString("/lights/%1/state").arg(id), QString("missing parameter to set warning device state"))); + requestOk = false; + } + if (!requestOk) + { + rsp.httpStatus = HttpStatusBadRequest; + return REQ_READY_SEND; + } bool isSmokeDetector = false; if (taskRef.lightNode->modelId() == QLatin1String("902010/24") || // Bitron Smoke Detector with siren taskRef.lightNode->modelId() == QLatin1String("SMSZB-120") || // Develco smoke sensor taskRef.lightNode->modelId() == QLatin1String("HESZB-120") || // Develco heat sensor with siren - taskRef.lightNode->modelId() == QLatin1String("FLSZB-110")) // Develco water leak sensor with siren + taskRef.lightNode->modelId() == QLatin1String("FLSZB-110")) // Develco water leak sensor with siren { isSmokeDetector = true; taskRef.lightNode->rx(); // otherwise device is marked as zombie and task is dropped @@ -1638,43 +1602,46 @@ int DeRestPluginPrivate::setWarningDeviceState(const ApiRequest &req, ApiRespons copyTaskReq(taskRef, task); task.taskType = TaskWarning; - if (alert == "none") - { - task.options = 0x00; // Warning mode 0 (no warning), No strobe - task.duration = 0; - } - else if (alert == "select") - { - task.options = isSmokeDetector - ? 0x12 // Warning mode 2 (fire), Strobe - : 0x14; // Warning mode 1 (burglar), Strobe - task.duration = 1; - } - else if (alert == "lselect") + if (!alert.isEmpty()) { - task.options = isSmokeDetector - ? 0x12 // Warning mode 2 (fire), Strobe - : 0x14; // Warning mode 1 (burglar), Strobe - task.duration = onTime > 0 ? onTime : 300; - } - else if (alert == "blink") - { - task.options = 0x04; // Warning mode 0 (no warning), Strobe - task.duration = onTime > 0 ? onTime : 300; - } + if (alert == "none") + { + task.options = 0x00; // Warning mode 0 (no warning), No strobe + task.duration = 0; + } + else if (alert == "select") + { + task.options = isSmokeDetector + ? 0x12 // Warning mode 2 (fire), Strobe + : 0x14; // Warning mode 1 (burglar), Strobe + task.duration = 1; + } + else if (alert == "lselect") + { + task.options = isSmokeDetector + ? 0x12 // Warning mode 2 (fire), Strobe + : 0x14; // Warning mode 1 (burglar), Strobe + task.duration = onTime > 0 ? onTime : 300; + } + else if (alert == "blink") + { + task.options = 0x04; // Warning mode 0 (no warning), Strobe + task.duration = onTime > 0 ? onTime : 300; + } - if (addTaskWarning(task, task.options, task.duration)) - { - QVariantMap rspItem; - QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/alert").arg(id)] = alert; - rspItem["success"] = rspItemState; - rsp.list.append(rspItem); - // Don't update write-only state.alert. - } - else - { - rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + if (addTaskWarning(task, task.options, task.duration)) + { + QVariantMap rspItem; + QVariantMap rspItemState; + rspItemState[QString("/lights/%1/state/alert").arg(id)] = alert; + rspItem["success"] = rspItemState; + rsp.list.append(rspItem); + // Don't update write-only state.alert. + } + else + { + rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/alert").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); + } } processTasks(); From 543c9cae5184be8cbedb0337834884da401188a2 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 8 Mar 2020 16:49:37 +0100 Subject: [PATCH 05/27] Update rest_lights.cpp Refactor `setLightState()`, see #2475. --- rest_lights.cpp | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/rest_lights.cpp b/rest_lights.cpp index dc43642946..da86dab3fe 100644 --- a/rest_lights.cpp +++ b/rest_lights.cpp @@ -679,7 +679,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (ok && hue >= 0 && hue <= 65535) { valueOk = true; - targetHue = hue == 65535 ? 65534 : hue; + targetHue = hue; } } else if (param == "sat" && taskRef.lightNode->item(RStateHue) && taskRef.lightNode->item(RStateSat)) @@ -709,7 +709,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) { paramOk = true; const int speed = map[param].toInt(&ok); - if (ok && speed > 0 && speed <= 255) + if (ok && speed > 0 && speed <= 65535) { valueOk = true; hasColorloopSpeed = true; @@ -833,13 +833,13 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) : ONOFF_COMMAND_ON; if (addTaskSetOnOff(task, cmd, taskRef.onTime, 0)) { + taskToLocalData(task); + isOn = true; QVariantMap rspItem; QVariantMap rspItemState; rspItemState[QString("/lights/%1/state/on").arg(id)] = true; rspItem["success"] = rspItemState; rsp.list.append(rspItem); - taskToLocalData(task); - isOn = true; } else { @@ -859,12 +859,12 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) } else if (addTaskSetBrightness(task, targetBri, false)) { + taskToLocalData(task); QVariantMap rspItem; QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/bri").arg(id)] = targetBri; + rspItemState[QString("/lights/%1/state/bri").arg(id)] = taskRef.lightNode->item(RStateBri)->toNumber(); rspItem["success"] = rspItemState; rsp.list.append(rspItem); - taskToLocalData(task); } else { @@ -895,12 +895,12 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) } else if (addTaskIncBrightness(task, targetBriInc)) { + taskToLocalData(task); QVariantMap rspItem; QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/bri_inc").arg(id)] = targetBriInc; + rspItemState[QString("/lights/%1/state/bri").arg(id)] = taskRef.lightNode->item(RStateBri)->toNumber(); rspItem["success"] = rspItemState; rsp.list.append(rspItem); - taskToLocalData(task); } else { @@ -914,7 +914,11 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) TaskItem task; copyTaskReq(taskRef, task); - if (addTaskSetColorLoop(task, false, colorloopSpeed)) + if (!isOn) + { + rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, effect, is not modifiable. Device is set to off."))); + } + else if (addTaskSetColorLoop(task, false, colorloopSpeed)) { QVariantMap rspItem; QVariantMap rspItemState; @@ -987,12 +991,12 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) } else if (addTaskIncColorTemperature(task, targetCtInc)) { + taskToLocalData(task); QVariantMap rspItem; QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/ct_inc").arg(id)] = targetCtInc; + rspItemState[QString("/lights/%1/state/ct").arg(id)] = taskRef.lightNode->item(RStateCt)->toNumber(); rspItem["success"] = rspItemState; rsp.list.append(rspItem); - taskToLocalData(task); } else { @@ -1006,10 +1010,16 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (!isOn) { - rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, ct_inc, is not modifiable. Device is set to off."))); + if (targetHue != 0xFFFF) + { + rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, hue, is not modifiable. Device is set to off."))); + } + if (targetSat != 0xFF) + { + rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, sat, is not modifiable. Device is set to off."))); + } } - - if (targetSat == 0xFF) // only state.hue + else if (targetSat == 0xFF) // only state.hue { ok = addTaskSetEnhancedHue(task, targetHue); } @@ -1096,7 +1106,11 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) TaskItem task; copyTaskReq(taskRef, task); - if (addTaskSetColorLoop(task, true, colorloopSpeed)) + if (!isOn) + { + rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, effect, is not modifiable. Device is set to off."))); + } + else if (addTaskSetColorLoop(task, true, colorloopSpeed)) { QVariantMap rspItem; QVariantMap rspItemState; From 4576a4496f9e389f7e19adb4a245adb7046311d0 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 8 Mar 2020 18:13:38 +0100 Subject: [PATCH 06/27] Update rest_lights.cpp --- rest_lights.cpp | 91 ++++++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/rest_lights.cpp b/rest_lights.cpp index da86dab3fe..4e8cab0082 100644 --- a/rest_lights.cpp +++ b/rest_lights.cpp @@ -548,21 +548,29 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) bool isOn = false; bool hasOn = false; bool targetOn = false; - quint8 targetBri = 0xFF; - qint16 targetBriInc = -32768; + bool hasBri = false; + quint8 targetBri = 0; + bool hasBriInc = false; + qint16 targetBriInc = 0; bool hasWrap = false; bool wrap = false; - double targetX = -1.0; - double targetY = -1.0; - quint16 targetCt = 0xFFFF; - qint16 targetCtInc = -32768; - quint16 targetHue = 0xFFFF; - quint8 targetSat = 0xFF; + bool hasXy = false; + double targetX = 0.0; + double targetY = 0.0; + bool hasCt = false; + quint16 targetCt = 0; + bool hasCtInc = false; + qint16 targetCtInc = 0; + bool hasHue = false; + quint16 targetHue = 0; + bool hasSat = false; + quint8 targetSat = 0; QString effect; bool hasColorloopSpeed = false; - quint16 colorloopSpeed = 15; + quint16 colorloopSpeed = 25; QString alert; - quint8 targetSpeed = 0xFF; + bool hasSpeed = 0; + quint8 targetSpeed = 0; // Check parameters. for (QVariantMap::const_iterator p = map.begin(); p != map.end(); p++) @@ -588,6 +596,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (map[param].type() == QVariant::String && map[param].toString() == "stop") // FIXME deprecate this nonsense { valueOk = true; + hasBriInc = true; targetBriInc = 0; } else if (map[param].type() == QVariant::Double) @@ -596,6 +605,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (ok && bri >= 0 && bri <= 255) { valueOk = true; + hasBri = true; targetBri = bri == 255 ? 254 : bri; } } @@ -610,6 +620,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (ok && briInc >= -254 && briInc <= 254) { valueOk = true; + hasBriInc = true; targetBriInc = briInc; } } @@ -628,6 +639,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (ok && x >= 0.0 && x <= 1.0 && y >= 0.0 && y <= 1.0) { valueOk = true; + hasXy = true; targetX = x; targetY = y; } @@ -648,6 +660,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (ok && ct >= ctMin && ct <= ctMax) { valueOk = true; + hasCt = true; targetCt = ct; } } @@ -667,6 +680,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (ok && ctInc >= -ctRange && ctInc <= ctRange) { valueOk = true; + hasCtInc = true; targetCtInc = ctInc; } } @@ -679,6 +693,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (ok && hue >= 0 && hue <= 65535) { valueOk = true; + hasHue = true; targetHue = hue; } } @@ -690,6 +705,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (ok && sat >= 0 && sat <= 255) { valueOk = true; + hasSat = true; targetSat = sat == 255 ? 254 : sat; } } @@ -736,6 +752,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (ok && speed >= 0 && speed <= 6) { valueOk = true; + hasSpeed = true; targetSpeed = speed; } } @@ -771,6 +788,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (map[param].type() == QVariant::Bool) { valueOk = true; + hasWrap = true; wrap = map[param].toBool(); } } @@ -789,7 +807,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) rsp.list.append(errorToMap(ERR_MISSING_PARAMETER, QString("/lights/%1/state").arg(id), QString("missing parameter, on or alert, for parameter, ontime"))); requestOk = false; } - if (hasWrap && targetBriInc != -32768) + if (hasWrap && !hasBriInc) { rsp.list.append(errorToMap(ERR_MISSING_PARAMETER, QString("/lights/%1/state").arg(id), QString("missing parameter, bri_inc, for parameter, wrap"))); requestOk = false; @@ -848,7 +866,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) } // state.bri trumps state.bri_inc - if (targetBri != 0xFF) + if (hasBri) { TaskItem task; copyTaskReq(taskRef, task); @@ -871,7 +889,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/bri").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - else if (targetBriInc != -32768) + else if (hasBriInc) { TaskItem task; copyTaskReq(taskRef, task); @@ -934,7 +952,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) } // state.xy trumps state.ct trumps state.ct_inc trumps state.hue, state.sat - if (targetX != -1.0 && targetY != -1.0) + if (hasXy) { TaskItem task; copyTaskReq(taskRef, task); @@ -957,7 +975,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/xy").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - else if (targetCt != 0xFFFF) + else if (hasCt) { TaskItem task; copyTaskReq(taskRef, task); @@ -980,7 +998,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/ct").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - else if (targetCtInc != -32768) + else if (hasCtInc) { TaskItem task; copyTaskReq(taskRef, task); @@ -1003,27 +1021,27 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/ct_inc").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - else if (targetHue != 0xFFFF || targetSat != 0xFF) + else if (hasHue || hasSat) { TaskItem task; copyTaskReq(taskRef, task); if (!isOn) { - if (targetHue != 0xFFFF) + if (hasHue) { rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, hue, is not modifiable. Device is set to off."))); } - if (targetSat != 0xFF) + if (hasSat) { rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, sat, is not modifiable. Device is set to off."))); } } - else if (targetSat == 0xFF) // only state.hue + else if (!hasSat) // only state.hue { ok = addTaskSetEnhancedHue(task, targetHue); } - else if (targetHue == 0xFFFF) // only state.sat + else if (!hasHue) // only state.sat { ok = addTaskSetSaturation(task, targetSat); } @@ -1036,8 +1054,8 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) if (ok) { // FIXME: do we need this? - quint16 hue = targetHue == 0xFFFF ? taskRef.lightNode->item(RStateHue)->toNumber() : targetHue; - quint8 sat = targetSat == 0xFF ? taskRef.lightNode->item(RStateHue)->toNumber() : targetSat; + quint16 hue = hasHue ? targetHue : taskRef.lightNode->item(RStateHue)->toNumber(); + quint8 sat = hasSat ? targetSat : taskRef.lightNode->item(RStateSat)->toNumber(); double r, g, b; double x, y; @@ -1076,7 +1094,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) } // End FIXME - if (targetHue != 0xFFFF) + if (hasHue) { QVariantMap rspItem; QVariantMap rspItemState; @@ -1084,7 +1102,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) rspItem["success"] = rspItemState; rsp.list.append(rspItem); } - if (targetSat != 0xFF) + if (hasSat) { QVariantMap rspItem; QVariantMap rspItemState; @@ -1194,7 +1212,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) } // state.speed - if (targetSpeed != 0xFF) + if (hasSpeed) { TaskItem task; copyTaskReq(taskRef, task); @@ -1278,11 +1296,12 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon bool requestOk = true; bool hasCmd = false; bool hasOn = false; - bool hasBri = false; - bool hasStop = false; bool targetOn = false; - quint8 targetLiftPct = 0xFF; - quint8 targetTiltPct = 0xFF; + bool hasLift = false; + bool hasStop = false; + quint8 targetLiftPct = 0; + bool hasTilt = false; + quint8 targetTiltPct = 0; // Check parameters. for (QVariantMap::const_iterator p = map.begin(); p != map.end(); p++) @@ -1299,6 +1318,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon valueOk = true; if (cluster == ANALOG_OUTPUT_CLUSTER_ID) { + hasLift = true; targetLiftPct = map["on"].toBool() ? 254 : 0; } else @@ -1323,7 +1343,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon if (ok && bri >= 0 && bri <= 255) { valueOk = true; - hasBri = true; + hasLift = true; targetLiftPct = bri * 100 / 254; } } @@ -1352,6 +1372,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon if (ok && sat >= 0 && sat <= 255) { valueOk = true; + hasTilt = true; targetTiltPct = sat * 100 / 254; } } @@ -1385,7 +1406,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon } // Some devices invert LiftPct. - if (targetLiftPct != 0xFF) + if (hasLift) { if (taskRef.lightNode->modelId().startsWith(QLatin1String("lumi.curtain"))) { @@ -1418,7 +1439,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1/state/bri_inc").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY))); } } - else if (targetLiftPct != 0xFF) + else if (hasLift) { bool ok; TaskItem task; @@ -1476,7 +1497,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon { QVariantMap rspItem; QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/bri").arg(id)] = hasBri ? map["bri"] : targetOn ? 254 : 0; + rspItemState[QString("/lights/%1/state/bri").arg(id)] = hasLift ? map["bri"] : targetOn ? 254 : 0; rspItem["success"] = rspItemState; rsp.list.append(rspItem); // Rely on attribute reporting to update the light state. @@ -1507,7 +1528,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon } // Handle TiltPct independent from Stop - LiftPct - Open/Close. - if (targetTiltPct != 0xFF) + if (hasTilt) { TaskItem task; copyTaskReq(taskRef, task); From 5582f7d82f7f770df8b45345c388d8fa3bee5f43 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 8 Mar 2020 18:45:01 +0100 Subject: [PATCH 07/27] Update rest_lights.cpp --- rest_lights.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_lights.cpp b/rest_lights.cpp index 4e8cab0082..55d6e8bcff 100644 --- a/rest_lights.cpp +++ b/rest_lights.cpp @@ -838,7 +838,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) TaskItem task; copyTaskReq(taskRef, task); - if (!isOn && targetBri != 0xFF) + if (!isOn && hasBri) { TaskItem task; copyTaskReq(taskRef, task); @@ -1562,7 +1562,7 @@ int DeRestPluginPrivate::setWarningDeviceState(const ApiRequest &req, ApiRespons bool ok; QString id = req.path[3]; - bool requestOk = false; + bool requestOk = true; bool hasCmd = false; QString alert; quint16 onTime = 0; From bcb7e8a76d410035e4906f9c4246d1ade289c04c Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 8 Mar 2020 21:40:50 +0100 Subject: [PATCH 08/27] Update zcl_tasks.cpp Implement _Off with Effect_ command. --- zcl_tasks.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/zcl_tasks.cpp b/zcl_tasks.cpp index 2f5226247c..2edbbed9e7 100644 --- a/zcl_tasks.cpp +++ b/zcl_tasks.cpp @@ -96,8 +96,8 @@ bool DeRestPluginPrivate::addTaskMoveLevel(TaskItem &task, bool withOnOff, bool */ 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_ON_WITH_TIMED_OFF); - if (!(cmd == ONOFF_COMMAND_ON || cmd == ONOFF_COMMAND_OFF || cmd == ONOFF_COMMAND_TOGGLE || cmd == ONOFF_COMMAND_ON_WITH_TIMED_OFF)) + 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; } @@ -114,7 +114,16 @@ bool DeRestPluginPrivate::addTaskSetOnOff(TaskItem &task, quint8 cmd, quint16 on deCONZ::ZclFCDirectionClientToServer | deCONZ::ZclFCDisableDefaultResponse); - if (cmd == ONOFF_COMMAND_ON_WITH_TIMED_OFF) + 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); From 6697b888a404d615884b7690c4429f3cb7152ffc Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 8 Mar 2020 21:47:54 +0100 Subject: [PATCH 09/27] Update rest_lights.cpp - Send _Off with Effect_ to turn off Hue lights; - Clear active colorloop when light is turned off. Hue lights do so automatically, but others (e.g. GLEDOPTO) don't. --- rest_lights.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/rest_lights.cpp b/rest_lights.cpp index 55d6e8bcff..40329a4934 100644 --- a/rest_lights.cpp +++ b/rest_lights.cpp @@ -1243,16 +1243,17 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) { if (taskRef.lightNode->isColorLoopActive()) { - taskRef.lightNode->setColorLoopSpeed(15); - taskRef.lightNode->setColorLoopActive(false); + TaskItem task; + copyTaskReq(taskRef, task); + addTaskSetColorLoop(task, false, colorloopSpeed); } - // send Off_with_effect(0, 0) - TaskItem task; copyTaskReq(taskRef, task); - - if (addTaskSetOnOff(task, ONOFF_COMMAND_OFF, 0, 0)) + const quint8 cmd = taskRef.lightNode->manufacturerCode() == VENDOR_PHILIPS // FIXME: use light capabilities + ? ONOFF_COMMAND_OFF_WITH_EFFECT + : ONOFF_COMMAND_OFF; + if (addTaskSetOnOff(task, cmd, 0, 0)) { QVariantMap rspItem; QVariantMap rspItemState; From a4938583b19c64060ef4cc2f6f4bb2500f691aa0 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 8 Mar 2020 22:07:56 +0100 Subject: [PATCH 10/27] Update rest_lights.cpp Error message when trying to modify colour attribute while colorloop is active. --- rest_lights.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/rest_lights.cpp b/rest_lights.cpp index 40329a4934..da81ea7b3f 100644 --- a/rest_lights.cpp +++ b/rest_lights.cpp @@ -961,6 +961,10 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) { rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, xy, is not modifiable. Device is set to off."))); } + else if (taskRef.lightNode->isColorLoopActive()) + { + rsp.list.append(errorToMap(ERR_PARAMETER_NOT_MODIFIEABLE, QString("/lights/%1/state").arg(id), QString("parameter, xy, is not modifiable. Colorloop is active."))); + } else if (addTaskSetXyColor(task, targetX, targetY)) { QVariantMap rspItem; @@ -984,6 +988,10 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) { rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, ct, is not modifiable. Device is set to off."))); } + else if (taskRef.lightNode->isColorLoopActive()) + { + rsp.list.append(errorToMap(ERR_PARAMETER_NOT_MODIFIEABLE, QString("/lights/%1/state").arg(id), QString("parameter, ct, is not modifiable. Colorloop is active."))); + } else if (addTaskSetColorTemperature(task, targetCt)) { QVariantMap rspItem; @@ -1007,6 +1015,10 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) { rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, ct_inc, is not modifiable. Device is set to off."))); } + else if (taskRef.lightNode->isColorLoopActive()) + { + rsp.list.append(errorToMap(ERR_PARAMETER_NOT_MODIFIEABLE, QString("/lights/%1/state").arg(id), QString("parameter, ct_inc, is not modifiable. Colorloop is active."))); + } else if (addTaskIncColorTemperature(task, targetCtInc)) { taskToLocalData(task); @@ -1037,6 +1049,18 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) rsp.list.append(errorToMap(ERR_DEVICE_OFF, QString("/lights/%1/state").arg(id), QString("parameter, sat, is not modifiable. Device is set to off."))); } } + else if (taskRef.lightNode->isColorLoopActive()) + { + if (hasHue) + { + rsp.list.append(errorToMap(ERR_PARAMETER_NOT_MODIFIEABLE, QString("/lights/%1/state").arg(id), QString("parameter, hue, is not modifiable. Colorloop is active."))); + } + if (hasSat) + { + rsp.list.append(errorToMap(ERR_PARAMETER_NOT_MODIFIEABLE, QString("/lights/%1/state").arg(id), QString("parameter, sat, is not modifiable. Colorloop is active."))); + } + } + else if (!hasSat) // only state.hue { ok = addTaskSetEnhancedHue(task, targetHue); From 22cda7362e5957ff96dbe951296d87ced9b69483 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sat, 14 Mar 2020 00:39:40 +0100 Subject: [PATCH 11/27] Update rest_lights.cpp Add message that `colormode` is not modifyable instead of not available. --- rest_lights.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rest_lights.cpp b/rest_lights.cpp index da81ea7b3f..8fa1620c57 100644 --- a/rest_lights.cpp +++ b/rest_lights.cpp @@ -732,6 +732,12 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) colorloopSpeed = speed; } } + else if (param == "colormode" && taskRef.lightNode->item(RStateColorMode)) { + paramOk = true; + valueOk = true; + rsp.list.append(errorToMap(ERR_PARAMETER_NOT_MODIFIEABLE, QString("/lights/%1/state").arg(id), QString("parameter, %1, is not modifiable.").arg(param))); + requestOk = false; + } else if (param == "alert" && taskRef.lightNode->item(RStateAlert)) { paramOk = true; From 28d3362f97417184e932433b4064efce4df1b1d0 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sat, 14 Mar 2020 17:54:40 +0100 Subject: [PATCH 12/27] Update rest_node_base.cpp Fix update ZCL value debug message --- rest_node_base.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_node_base.cpp b/rest_node_base.cpp index 1e53539b72..f2146fae2d 100644 --- a/rest_node_base.cpp +++ b/rest_node_base.cpp @@ -288,7 +288,7 @@ void RestNodeBase::setZclValue(NodeValue::UpdateType updateType, quint8 endpoint if (DBG_IsEnabled(DBG_INFO_L2)) { - DBG_Printf(DBG_INFO_L2, "update ZCL value 0x%04X/0x%04X for ep: 0x%02X 0x%016llX after %lld s\n", endpoint, clusterId, attributeId, address().ext(), i->timestamp.secsTo(now)); + DBG_Printf(DBG_INFO_L2, "0x%016llX: update ZCL value 0x%02X/0x%04X/0x%04X after %lld s\n", address().ext(), endpoint, clusterId, attributeId, i->timestamp.secsTo(now)); } return; } @@ -306,7 +306,7 @@ void RestNodeBase::setZclValue(NodeValue::UpdateType updateType, quint8 endpoint val.updateType = updateType; val.value = value; - DBG_Printf(DBG_INFO_L2, "added ZCL value 0x%04X/0x%04X for ep: 0x%02X 0x%016llX\n", clusterId, attributeId, endpoint, address().ext()); + DBG_Printf(DBG_INFO_L2, "0x%016llX: added ZCL value 0x%02/0x%04X/0x%04X\n", address().ext(), endpoint, clusterId, attributeId); m_values.push_back(val); } From 5ae403fd3adc295bec5c69d600eb84ca1a9c5c59 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sat, 14 Mar 2020 18:01:42 +0100 Subject: [PATCH 13/27] Update bindings.cpp Samsum SmartThings multipupose sensors, see #1848: - Don't configure event reporting for orientation attributes (as these are already sent in the _IAS Zone State Notification_ command); - Set periodic reporting of these to every 5 minutes, in line with temperature and open/close reporting for the same sensor. --- bindings.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/bindings.cpp b/bindings.cpp index cd942a83bf..bda1d96379 100644 --- a/bindings.cpp +++ b/bindings.cpp @@ -920,16 +920,28 @@ bool DeRestPluginPrivate::sendConfigureReportingRequest(BindingTask &bt) { bt.restNode->setZclValue(NodeValue::UpdateInvalid, bt.binding.srcEndpoint, bt.binding.clusterId, IAS_ZONE_CLUSTER_ATTR_ZONE_STATUS_ID, dummy); } - rq.minInterval = 1; - rq.maxInterval = 300; const Sensor *sensor = dynamic_cast(bt.restNode); - const ResourceItem *item = sensor ? sensor->item(RConfigDuration) : nullptr; - if (item && item->toNumber() > 15 && item->toNumber() <= UINT16_MAX) + if (sensor->type() == QLatin1String("ZHAVibration") && sensor->modelId() == QLatin1String("multi")) // FIXME: check if this also applies to other Samjin sensors + // if (bt.restNode->node()->nodeDescriptor().manufacturerCode() == VENDOR_SAMJIN) { - rq.maxInterval = static_cast(item->toNumber()); - rq.maxInterval -= 5; // report before going presence: false + // Only configure periodic reports, as events are already sent though zone status change notification commands + rq.minInterval = 300; + rq.maxInterval = 300; + } + else + { + rq.minInterval = 1; + rq.maxInterval = 300; + + const ResourceItem *item = sensor ? sensor->item(RConfigDuration) : nullptr; + + if (item && item->toNumber() > 15 && item->toNumber() <= UINT16_MAX) + { + rq.maxInterval = static_cast(item->toNumber()); + rq.maxInterval -= 5; // report before going presence: false + } } rq.dataType = deCONZ::Zcl16BitBitMap; @@ -1431,7 +1443,7 @@ bool DeRestPluginPrivate::sendConfigureReportingRequest(BindingTask &bt) rq.dataType = deCONZ::Zcl16BitInt; rq.attributeId = 0x0012; // acceleration x rq.minInterval = 1; - rq.maxInterval = 3600; + rq.maxInterval = 300; rq.reportableChange16bit = 1; rq.manufacturerCode = VENDOR_SAMJIN; @@ -1439,7 +1451,7 @@ bool DeRestPluginPrivate::sendConfigureReportingRequest(BindingTask &bt) rq2.dataType = deCONZ::Zcl16BitInt; rq2.attributeId = 0x0013; // acceleration y rq2.minInterval = 1; - rq2.maxInterval = 3600; + rq2.maxInterval = 300; rq2.reportableChange16bit = 1; rq2.manufacturerCode = VENDOR_SAMJIN; @@ -1447,7 +1459,7 @@ bool DeRestPluginPrivate::sendConfigureReportingRequest(BindingTask &bt) rq3.dataType = deCONZ::Zcl16BitInt; rq3.attributeId = 0x0014; // acceleration z rq3.minInterval = 1; - rq3.maxInterval = 3600; + rq3.maxInterval = 300; rq3.reportableChange16bit = 1; rq3.manufacturerCode = VENDOR_SAMJIN; From 23addf853a64944703d207d6dadfece413577ddc Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sat, 14 Mar 2020 18:02:25 +0100 Subject: [PATCH 14/27] Update database.cpp Samsumg SmartThings multipurpose sensor, see #1848: - Add `config.duration`. --- database.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/database.cpp b/database.cpp index bc71ed9a07..addc7c6050 100644 --- a/database.cpp +++ b/database.cpp @@ -3141,6 +3141,8 @@ static int sqliteLoadAllSensorsCallback(void *user, int ncols, char **colval , c item = sensor.addItem(DataTypeInt16, RStateOrientationX); item = sensor.addItem(DataTypeInt16, RStateOrientationY); item = sensor.addItem(DataTypeInt16, RStateOrientationZ); + item = sensor.addItem(DataTypeUInt16, RConfigDuration); + item->setValue(0); } } else if (sensor.type().endsWith(QLatin1String("Water"))) From a2658771abce8854b44f7b6242b9844e1c76e5b5 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sat, 14 Mar 2020 18:02:57 +0100 Subject: [PATCH 15/27] Update de_web_plugin.cpp Samsumg SmartThings multipurpose sensor, see #1848: - Add `config.duration`. - Refactor logic to update `state.vibration`. --- de_web_plugin.cpp | 55 +++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/de_web_plugin.cpp b/de_web_plugin.cpp index da8f4895cd..57e89131c2 100644 --- a/de_web_plugin.cpp +++ b/de_web_plugin.cpp @@ -5271,6 +5271,8 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const SensorFi item = sensorNode.addItem(DataTypeInt16, RStateOrientationX); item = sensorNode.addItem(DataTypeInt16, RStateOrientationY); item = sensorNode.addItem(DataTypeInt16, RStateOrientationZ); + item = sensorNode.addItem(DataTypeUInt16, RConfigDuration); + item->setValue(0); } if (fingerPrint.hasInCluster(IAS_ZONE_CLUSTER_ID)) // POLL_CONTROL_CLUSTER_ID @@ -6480,6 +6482,9 @@ void DeRestPluginPrivate::updateSensorNode(const deCONZ::NodeEvent &event) } else if (event.clusterId() == SAMJIN_CLUSTER_ID) { + bool updated = false; + bool vibration = false; + for (;ia != enda; ++ia) { if (std::find(event.attributeIds().begin(), @@ -6489,16 +6494,6 @@ void DeRestPluginPrivate::updateSensorNode(const deCONZ::NodeEvent &event) continue; } - if (ia->id() == 0x0012 || ia->id() == 0x0013 || ia->id() == 0x0014) // accelerate - { - ResourceItem *item = i->item(RStateVibration); - if (item) - { - item->setValue(true); - enqueueEvent(Event(RSensors, RStateVibration, i->id(), item)); - i->durationDue = item->lastSet().addSecs(65); - } - } if (ia->id() == 0x0012) // accelerate x { @@ -6513,15 +6508,14 @@ void DeRestPluginPrivate::updateSensorNode(const deCONZ::NodeEvent &event) if (item) { item->setValue(ia->numericValue().s16); + updated = true; if (item->lastSet() == item->lastChanged()) { Event e(RSensors, item->descriptor().suffix, i->id(), item); enqueueEvent(e); + vibration = true; } - i->setNeedSaveDatabase(true); - i->updateStateTimestamp(); - enqueueEvent(Event(RSensors, RStateLastUpdated, i->id())); } } else if (ia->id() == 0x0013) // accelerate y @@ -6537,15 +6531,14 @@ void DeRestPluginPrivate::updateSensorNode(const deCONZ::NodeEvent &event) if (item) { item->setValue(ia->numericValue().s16); + updated = true; if (item->lastSet() == item->lastChanged()) { Event e(RSensors, item->descriptor().suffix, i->id(), item); enqueueEvent(e); + vibration = true; } - i->setNeedSaveDatabase(true); - i->updateStateTimestamp(); - enqueueEvent(Event(RSensors, RStateLastUpdated, i->id())); } } else if (ia->id() == 0x0014) // accelerate z @@ -6561,18 +6554,42 @@ void DeRestPluginPrivate::updateSensorNode(const deCONZ::NodeEvent &event) if (item) { item->setValue(ia->numericValue().s16); + updated = true; if (item->lastSet() == item->lastChanged()) { Event e(RSensors, item->descriptor().suffix, i->id(), item); enqueueEvent(e); + vibration = true; } - i->setNeedSaveDatabase(true); - i->updateStateTimestamp(); - enqueueEvent(Event(RSensors, RStateLastUpdated, i->id())); } } } + + if (updated) + { + if (vibration) + { + { + ResourceItem *item = i->item(RStateVibration); + if (item) + { + item->setValue(true); + enqueueEvent(Event(RSensors, RStateVibration, i->id(), item)); + + // prepare to set vibration to false automatically + ResourceItem *item2 = i->item(RConfigDuration); + if (item2 && item2->toNumber() > 0) + { + i->durationDue = item->lastSet().addSecs(item2->toNumber()); + } + } + } + } + i->setNeedSaveDatabase(true); + i->updateStateTimestamp(); + enqueueEvent(Event(RSensors, RStateLastUpdated, i->id())); + } } else if (event.clusterId() == BASIC_CLUSTER_ID) { From a3bb6805de66d2ffa25b5f6817a407d44ed77a90 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 15 Mar 2020 10:57:48 +0100 Subject: [PATCH 16/27] Update rest_lights.cpp Fix setting `state.on` for Aqara B1, see #1654. --- rest_lights.cpp | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/rest_lights.cpp b/rest_lights.cpp index 8fa1620c57..29f9070e42 100644 --- a/rest_lights.cpp +++ b/rest_lights.cpp @@ -1329,6 +1329,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon bool hasOn = false; bool targetOn = false; bool hasLift = false; + bool hasBri = false; bool hasStop = false; quint8 targetLiftPct = 0; bool hasTilt = false; @@ -1347,16 +1348,8 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon if (map["on"].type() == QVariant::Bool) { valueOk = true; - if (cluster == ANALOG_OUTPUT_CLUSTER_ID) - { - hasLift = true; - targetLiftPct = map["on"].toBool() ? 254 : 0; - } - else - { - hasOn = true; - targetOn = map["on"].toBool(); - } + hasOn = true; + targetOn = map["on"].toBool(); } } else if (param == "bri" && taskRef.lightNode->item(RStateBri)) @@ -1374,6 +1367,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon if (ok && bri >= 0 && bri <= 255) { valueOk = true; + hasBri = true; // for response value hasLift = true; targetLiftPct = bri * 100 / 254; } @@ -1430,10 +1424,10 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon return REQ_READY_SEND; } - if (hasOn && targetOn && targetLiftPct == 0) + if (cluster == ANALOG_OUTPUT_CLUSTER_ID && hasOn && !hasLift) { - // handle {"on": true, "bri": 0} - targetLiftPct = 1; + hasLift = true; + targetLiftPct = targetOn ? 100 : 0; } // Some devices invert LiftPct. @@ -1446,7 +1440,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon else if (taskRef.lightNode->modelId() == QLatin1String("Shutter switch with neutral")) { // Legrand invert bri and don't support other value than 0 - targetLiftPct = targetLiftPct == 0 ? 254 : 0; + targetLiftPct = targetLiftPct == 0 ? 100 : 0; } } @@ -1528,7 +1522,7 @@ int DeRestPluginPrivate::setWindowCoveringState(const ApiRequest &req, ApiRespon { QVariantMap rspItem; QVariantMap rspItemState; - rspItemState[QString("/lights/%1/state/bri").arg(id)] = hasLift ? map["bri"] : targetOn ? 254 : 0; + rspItemState[QString("/lights/%1/state/bri").arg(id)] = hasBri ? map["bri"] : targetOn ? 254 : 0; rspItem["success"] = rspItemState; rsp.list.append(rspItem); // Rely on attribute reporting to update the light state. From 2c2edb34e1d794d7345cd058de16721e08f1240d Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sat, 21 Mar 2020 17:05:47 +0100 Subject: [PATCH 17/27] Update general.xml Add optional OTAU attributes. --- general.xml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/general.xml b/general.xml index cb59b1a227..2e275d3b52 100644 --- a/general.xml +++ b/general.xml @@ -1076,7 +1076,9 @@ that measures a physical quantity that can take on one of a number of discrete s - + + + @@ -2619,19 +2621,19 @@ devices can operate on either battery or mains power, and can have a wide variet Enable Sensor - + X-Value - + Y-Value - + Z-Value - + From 969f863a3d706142e04e7fe633bed99b8ea36de1 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 22 Mar 2020 21:29:44 +0100 Subject: [PATCH 18/27] Long press for icasa Keypad Scene buttons. See #2617. --- de_web_plugin.cpp | 16 ++++++++++++---- sensor.cpp | 6 ++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/de_web_plugin.cpp b/de_web_plugin.cpp index 57e89131c2..b780bb2a98 100644 --- a/de_web_plugin.cpp +++ b/de_web_plugin.cpp @@ -3486,11 +3486,10 @@ void DeRestPluginPrivate::checkSensorButtonEvent(Sensor *sensor, const deCONZ::A ok = true; } } - else if (ind.clusterId() == IAS_ZONE_CLUSTER_ID) + else if (ind.clusterId() == SCENE_CLUSTER_ID && zclFrame.commandId() == 0x04) // store scene { - ok = false; - // following works for Samjin button - if (zclFrame.payload().size() == 6 && buttonMap->zclParam0 == zclFrame.payload().at(0)) + ok = false; // payload: groupId, sceneId + if (zclFrame.payload().size() >= 3 && buttonMap->zclParam0 == zclFrame.payload().at(2)) { ok = true; } @@ -3585,6 +3584,15 @@ void DeRestPluginPrivate::checkSensorButtonEvent(Sensor *sensor, const deCONZ::A } } } + else if (ind.clusterId() == IAS_ZONE_CLUSTER_ID) + { + ok = false; + // following works for Samjin button + if (zclFrame.payload().size() == 6 && buttonMap->zclParam0 == zclFrame.payload().at(0)) + { + ok = true; + } + } else if (ind.clusterId() == COLOR_CLUSTER_ID && (zclFrame.commandId() == 0x4b && zclFrame.payload().size() >= 7) ) // move color temperature { diff --git a/sensor.cpp b/sensor.cpp index de11c87664..be5a6e3733 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -480,11 +480,17 @@ static const Sensor::ButtonMap icasaKeypadMap[] = { { Sensor::ModeScenes, 0x01, 0x0008, 0x07, 0, S_BUTTON_2 + S_BUTTON_ACTION_LONG_RELEASED, "Stop_ (with on/off)" }, // Scene buttons { Sensor::ModeScenes, 0x01, 0x0005, 0x05, 1, S_BUTTON_3 + S_BUTTON_ACTION_SHORT_RELEASED, "Recall scene 1" }, + { Sensor::ModeScenes, 0x01, 0x0005, 0x04, 1, S_BUTTON_3 + S_BUTTON_ACTION_LONG_RELEASED, "Store scene 1" }, { Sensor::ModeScenes, 0x01, 0x0005, 0x05, 2, S_BUTTON_4 + S_BUTTON_ACTION_SHORT_RELEASED, "Recall scene 2" }, + { Sensor::ModeScenes, 0x01, 0x0005, 0x04, 2, S_BUTTON_4 + S_BUTTON_ACTION_LONG_RELEASED, "Store scene 2" }, { Sensor::ModeScenes, 0x01, 0x0005, 0x05, 3, S_BUTTON_5 + S_BUTTON_ACTION_SHORT_RELEASED, "Recall scene 3" }, + { Sensor::ModeScenes, 0x01, 0x0005, 0x04, 3, S_BUTTON_5 + S_BUTTON_ACTION_LONG_RELEASED, "Store scene 3" }, { Sensor::ModeScenes, 0x01, 0x0005, 0x05, 4, S_BUTTON_6 + S_BUTTON_ACTION_SHORT_RELEASED, "Recall scene 4" }, + { Sensor::ModeScenes, 0x01, 0x0005, 0x04, 4, S_BUTTON_6 + S_BUTTON_ACTION_LONG_RELEASED, "Store scene 4" }, { Sensor::ModeScenes, 0x01, 0x0005, 0x05, 5, S_BUTTON_7 + S_BUTTON_ACTION_SHORT_RELEASED, "Recall scene 5" }, + { Sensor::ModeScenes, 0x01, 0x0005, 0x04, 5, S_BUTTON_7 + S_BUTTON_ACTION_LONG_RELEASED, "Store scene 5" }, { Sensor::ModeScenes, 0x01, 0x0005, 0x05, 6, S_BUTTON_8 + S_BUTTON_ACTION_SHORT_RELEASED, "Recall scene 6" }, + { Sensor::ModeScenes, 0x01, 0x0005, 0x04, 6, S_BUTTON_8 + S_BUTTON_ACTION_LONG_RELEASED, "Store scene 6" }, // end { Sensor::ModeNone, 0x00, 0x0000, 0x00, 0, 0, nullptr } }; From 875e4ae34ab986e9ceda5b080df11c30a7f42f0d Mon Sep 17 00:00:00 2001 From: ebaauw Date: Fri, 3 Apr 2020 20:03:26 +0200 Subject: [PATCH 19/27] Update rest_groups.cpp Improve handling of scene states (PUT `/groups/#/scenes/#/lights/#/state`), see #2507: - Handle `ct` in scene light state; - Set colormode for scene light state. --- rest_groups.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/rest_groups.cpp b/rest_groups.cpp index c01d20645a..45639c67f5 100644 --- a/rest_groups.cpp +++ b/rest_groups.cpp @@ -3025,11 +3025,13 @@ int DeRestPluginPrivate::modifyScene(const ApiRequest &req, ApiResponse &rsp) uint tt = 0; uint16_t xy_x; uint16_t xy_y; + uint16_t ct = 0; bool hasOn = false; bool hasBri = false; bool hasTt = false; bool hasXy = false; + bool hasCt = false; // on if (map.contains("on")) @@ -3084,6 +3086,23 @@ int DeRestPluginPrivate::modifyScene(const ApiRequest &req, ApiResponse &rsp) } } + if (map.contains("ct")) + { + bool ok; + ct = map["ct"].toUInt(&ok); + + if (ok && map["ct"].type() == QVariant::Double && (ct < 1000)) + { + hasCt = true; + } + else + { + rsp.list.append(errorToMap(ERR_INVALID_VALUE, QString("/groups/%1/scenes/%2/lights/%3/state/ct").arg(gid).arg(sid).arg(lid), QString("invalid value, %1, for parameter ct").arg(ct))); + rsp.httpStatus = HttpStatusBadRequest; + return REQ_READY_SEND; + } + } + // xy if (map.contains("xy")) { @@ -3157,9 +3176,15 @@ int DeRestPluginPrivate::modifyScene(const ApiRequest &req, ApiResponse &rsp) } if (hasXy) { + l->setColorMode(QLatin1String("xy")); l->setX(xy_x); l->setY(xy_y); } + else if (hasCt) + { + l->setColorMode(QLatin1String("ct")); + l->setColorTemperature(ct); + } if (!modifyScene(group, i->id)) { From 0f94124244ef882300de6aa01e21ca21ae0cf1f8 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Fri, 3 Apr 2020 20:04:35 +0200 Subject: [PATCH 20/27] Update zcl_tasks.cpp Improve handling of scene states (PUT `/groups/#/scenes/#/lights/#/state`), see #2507: - Bug fix: malformed packet on setting scene light state; - iCasa filament light stores `ct` in _Enhanced Hue_; --- zcl_tasks.cpp | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/zcl_tasks.cpp b/zcl_tasks.cpp index 2edbbed9e7..011b9dc078 100644 --- a/zcl_tasks.cpp +++ b/zcl_tasks.cpp @@ -1285,32 +1285,10 @@ bool DeRestPluginPrivate::addTaskAddScene(TaskItem &task, uint16_t groupId, uint { stream << (uint16_t)0x0300; // color cluster stream << (uint8_t)11; // size - if (l->colorMode() == QLatin1String("xy")) - { - stream << l->x(); - stream << l->y(); - stream << l->enhancedHue(); - stream << l->saturation(); -#if 0 - stream << l->x(); - stream << l->y(); - - if (task.lightNode->manufacturerCode() == VENDOR_OSRAM || - task.lightNode->manufacturerCode() == VENDOR_OSRAM_STACK) - { - stream << l->enhancedHue(); - stream << l->saturation(); - } - else - { - stream << (quint16)0; //enhanced hue - stream << (quint8)0; // saturation - } -#endif - } - else if (l->colorMode() == QLatin1String("ct")) + if (l->colorMode() == QLatin1String("ct")) { quint16 x,y; + quint16 enhancedHue = 0; ResourceItem *ctMin = task.lightNode->item(RConfigCtMin); ResourceItem *ctMax = task.lightNode->item(RConfigCtMax); @@ -1332,6 +1310,13 @@ bool DeRestPluginPrivate::addTaskAddScene(TaskItem &task, uint16_t groupId, uint x = l->colorTemperature(); y = 0; } + else if (task.lightNode->modelId().startsWith(QLatin1String("ICZB-F"))) + { + // quirks mode icasa filament lights store color temperature in hue + enhancedHue = l->colorTemperature(); + x = 0; + y = 0; + } else { quint16 ct = l->colorTemperature(); @@ -1358,10 +1343,10 @@ bool DeRestPluginPrivate::addTaskAddScene(TaskItem &task, uint16_t groupId, uint stream << x; stream << y; - stream << (quint16)0; //enhanced hue + stream << enhancedHue; stream << (quint8)0; // saturation } - else if (l->colorMode() == QLatin1String("hs")) + else { stream << l->x(); stream << l->y(); From 5774d90633cf25254ace2f39abe49fb8bcff0123 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Fri, 3 Apr 2020 20:07:00 +0200 Subject: [PATCH 21/27] Update resource.cpp - Add `lastannounced` and `lastseen` attributes, see 2590; - Store `state.lastupdated` in msec resolution, see #1687. --- resource.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resource.cpp b/resource.cpp index 4cbfa72c97..4b6152a082 100644 --- a/resource.cpp +++ b/resource.cpp @@ -32,6 +32,8 @@ const char *RAttrType = "attr/type"; const char *RAttrClass = "attr/class"; const char *RAttrUniqueId = "attr/uniqueid"; const char *RAttrSwVersion = "attr/swversion"; +const char *RAttrLastAnnounce = "attr/lastannounced"; +const char *RAttrLastSeen = "attr/lastseen"; const char *RActionScene = "action/scene"; @@ -164,6 +166,8 @@ void initResourceDescriptors() rItemDescriptors.emplace_back(ResourceItemDescriptor(DataTypeString, RAttrClass)); rItemDescriptors.emplace_back(ResourceItemDescriptor(DataTypeString, RAttrUniqueId)); rItemDescriptors.emplace_back(ResourceItemDescriptor(DataTypeString, RAttrSwVersion)); + rItemDescriptors.emplace_back(ResourceItemDescriptor(DataTypeTime, RAttrLastAnnounce)); + rItemDescriptors.emplace_back(ResourceItemDescriptor(DataTypeTime, RAttrLastSeen)); rItemDescriptors.emplace_back(ResourceItemDescriptor(DataTypeBool, RStateAlarm)); rItemDescriptors.emplace_back(ResourceItemDescriptor(DataTypeString, RStateAlert)); @@ -373,7 +377,7 @@ const QString &ResourceItem::toString() const QDateTime dt; dt.setOffsetFromUtc(0); dt.setMSecsSinceEpoch(m_num); - *m_str = dt.toString("yyyy-MM-ddTHH:mm:ss"); + *m_str = dt.toString(m_rid.suffix == RStateLastUpdated ? "yyyy-MM-ddTHH:mm:ss.zzz" : "yyyy-MM-ddTHH:mm:ss"); } else { From b224f4f09f5dbd3711d188d44ab8b3b6e88d1aad Mon Sep 17 00:00:00 2001 From: ebaauw Date: Fri, 3 Apr 2020 20:07:12 +0200 Subject: [PATCH 22/27] Update resource.h Add `lastannounced` and `lastseen` attributes, see 2590. --- resource.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resource.h b/resource.h index 99ec1f659a..0e62a771ec 100644 --- a/resource.h +++ b/resource.h @@ -47,6 +47,8 @@ extern const char *RAttrType; extern const char *RAttrClass; extern const char *RAttrUniqueId; extern const char *RAttrSwVersion; +extern const char *RAttrLastAnnounce; +extern const char *RAttrLastSeen; extern const char *RActionScene; From 268c2cfca4d68a3b3b5b02b0734a365b1633e514 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Fri, 3 Apr 2020 20:10:35 +0200 Subject: [PATCH 23/27] Update rest_lights.cpp - Implement `lastseen` attribute (in msec resolution), see 2590; - Bug fix: `state.ontime` not honoured for sirens (warning devices); - Include light `state` in light added event, see #2625. --- rest_lights.cpp | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/rest_lights.cpp b/rest_lights.cpp index 29f9070e42..ea899834aa 100644 --- a/rest_lights.cpp +++ b/rest_lights.cpp @@ -281,6 +281,7 @@ bool DeRestPluginPrivate::lightToMap(const ApiRequest &req, const LightNode *lig map["uniqueid"] = lightNode->uniqueId(); map["name"] = lightNode->name(); map["type"] = lightNode->type(); + map["lastseen"] = lightNode->lastRx().toUTC().toString("yyyy-MM-ddTHH:mm:ss.zzz"); // Amazon Echo quirks mode if (req.mode == ApiModeEcho) @@ -1618,7 +1619,7 @@ int DeRestPluginPrivate::setWarningDeviceState(const ApiRequest &req, ApiRespons const uint ot = map[param].toUInt(&ok); if (ok && ot > 0 && ot < 0xFFFF) { valueOk = true; - taskRef.onTime = ot; + onTime = ot; } } } @@ -1633,7 +1634,7 @@ int DeRestPluginPrivate::setWarningDeviceState(const ApiRequest &req, ApiRespons requestOk = false; } } - if (taskRef.onTime > 0 && alert.isEmpty()) { + if (onTime > 0 && alert.isEmpty()) { rsp.list.append(errorToMap(ERR_MISSING_PARAMETER, QString("/lights/%1/state").arg(id), QString("missing parameter, alert, for parameter, ontime"))); requestOk = false; } @@ -2363,20 +2364,24 @@ void DeRestPluginPrivate::handleLightEvent(const Event &e) } } } - else if (e.what() == RAttrName) + if (strncmp(e.what(), "attr/", 5) == 0) { - QVariantMap map; - map["t"] = QLatin1String("event"); - map["e"] = QLatin1String("changed"); - map["r"] = QLatin1String("lights"); - map["id"] = e.id(); - map["uniqueid"] = lightNode->uniqueId(); - - if (e.what() == RAttrName) // new attributes might be added in future + ResourceItem *item = lightNode->item(e.what()); + if (item) { - map["name"] = lightNode->name(); + QVariantMap map; + map["t"] = QLatin1String("event"); + map["e"] = QLatin1String("changed"); + map["r"] = QLatin1String("lights"); + map["id"] = e.id(); + map["uniqueid"] = lightNode->uniqueId(); + + // For now, don't collect top-level attributes into a single event. + const char *key = item->descriptor().suffix + 5; + map[key] = item->toVariant(); + + webSocketServer->broadcastTextMessage(Json::serialize(map)); } - webSocketServer->broadcastTextMessage(Json::serialize(map)); } else if (e.what() == REventAdded) { @@ -2384,12 +2389,20 @@ void DeRestPluginPrivate::handleLightEvent(const Event &e) res["name"] = lightNode->name(); searchLightsResult[lightNode->id()] = res; + QVariantMap lmap; + QHttpRequestHeader hdr; // dummy + QStringList path; // dummy + ApiRequest req(hdr, path, nullptr, QLatin1String("")); // dummy + req.mode = ApiModeNormal; + lightToMap(req, lightNode, lmap); + QVariantMap map; map["t"] = QLatin1String("event"); map["e"] = QLatin1String("added"); map["r"] = QLatin1String("lights"); map["id"] = e.id(); map["uniqueid"] = lightNode->uniqueId(); + map["light"] = lmap; webSocketServer->broadcastTextMessage(Json::serialize(map)); } From b3cfeb58e9c850c8383bc72fc4abe9ab12138497 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Fri, 3 Apr 2020 20:11:51 +0200 Subject: [PATCH 24/27] Update sensor.cpp --- sensor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/sensor.cpp b/sensor.cpp index be5a6e3733..18f2f46071 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -986,7 +986,6 @@ int Sensor::rxCounter() const return m_rxCounter; } - /*! Returns the sensor manufacturer. */ const QString &Sensor::manufacturer() const From e1d2208efc9c4de9e7a6f19034c3a73d23945cd4 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Fri, 3 Apr 2020 20:15:20 +0200 Subject: [PATCH 25/27] Update rest_sensors.cpp - Show `state.lastupdated` in msec resolution, see #1687; - Implement `lastsceen`. --- rest_sensors.cpp | 53 +++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/rest_sensors.cpp b/rest_sensors.cpp index e1253a99a0..bb709aba82 100644 --- a/rest_sensors.cpp +++ b/rest_sensors.cpp @@ -1708,12 +1708,18 @@ bool DeRestPluginPrivate::sensorToMap(const Sensor *sensor, QVariantMap &map, co { const char *key = item->descriptor().suffix + 6; - if (rid.suffix == RStateLastUpdated && (!item->lastSet().isValid() || (item->lastSet().date().year() < 2000))) + if (rid.suffix == RStateLastUpdated) { - state[key] = QLatin1String("none"); - continue; + if (!item->lastSet().isValid() || item->lastSet().date().year() < 2000) + { + state[key] = QLatin1String("none"); + } + else + { + state[key] = item->toVariant().toDateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz"); + } } - if (rid.suffix == RStateOrientationX) + else if (rid.suffix == RStateOrientationX) { ix = item; } @@ -1742,6 +1748,10 @@ bool DeRestPluginPrivate::sensorToMap(const Sensor *sensor, QVariantMap &map, co //sensor map["name"] = sensor->name(); map["type"] = sensor->type(); + if (sensor->type().startsWith(QLatin1String("Z"))) // ZigBee sensor + { + map["lastseen"] = sensor->lastRx().toUTC().toString("yyyy-MM-ddTHH:mm:ss.zzz"); + } if (req.path.size() > 2 && req.path[2] == QLatin1String("devices")) { @@ -1981,6 +1991,26 @@ void DeRestPluginPrivate::handleSensorEvent(const Event &e) } } } + else if (strncmp(e.what(), "attr/", 5) == 0) + { + ResourceItem *item = sensor->item(e.what()); + if (item) + { + QVariantMap map; + map["t"] = QLatin1String("event"); + map["e"] = QLatin1String("changed"); + map["r"] = QLatin1String("sensors"); + map["id"] = e.id(); + map["uniqueid"] = sensor->uniqueId(); + QVariantMap config; + + // For now, don't collect top-level attributes into a single event. + const char *key = item->descriptor().suffix + 5; + map[key] = item->toVariant(); + + webSocketServer->broadcastTextMessage(Json::serialize(map)); + } + } else if (e.what() == REventAdded) { checkSensorGroup(sensor); @@ -2030,21 +2060,6 @@ void DeRestPluginPrivate::handleSensorEvent(const Event &e) webSocketServer->broadcastTextMessage(Json::serialize(map)); } - else if (e.what() == RAttrName) - { - QVariantMap map; - map["t"] = QLatin1String("event"); - map["e"] = QLatin1String("changed"); - map["r"] = QLatin1String("sensors"); - map["id"] = e.id(); - map["uniqueid"] = sensor->uniqueId(); - - if (e.what() == RAttrName) // new attributes might be added in future - { - map["name"] = sensor->name(); - } - webSocketServer->broadcastTextMessage(Json::serialize(map)); - } else if (e.what() == REventValidGroup) { checkOldSensorGroups(sensor); From 9648c37a96e8691ba4d7dbc06e729bb8ccdbd116 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Fri, 3 Apr 2020 20:44:22 +0200 Subject: [PATCH 26/27] Update de_web_plugin.cpp Set `rx()` for ZGPSwitch `/sensors`, so `lastseen` is updated. --- de_web_plugin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/de_web_plugin.cpp b/de_web_plugin.cpp index b780bb2a98..bd028076b8 100644 --- a/de_web_plugin.cpp +++ b/de_web_plugin.cpp @@ -1002,6 +1002,8 @@ void DeRestPluginPrivate::gpProcessButtonEvent(const deCONZ::GpDataIndication &i { return; } + sensor->rx(); + quint32 btn = ind.gpdCommandId(); if (sensor->modelId() == QLatin1String("FOHSWITCH")) { From 12e6e8673f3e6a2a59a0c4658228057f8f22580c Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sat, 4 Apr 2020 11:35:13 +0200 Subject: [PATCH 27/27] Update sensor.cpp - Handle restore of `state.lastupdated` with both second and milliseccond resolution. --- sensor.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sensor.cpp b/sensor.cpp index 18f2f46071..7a23bb3ad2 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1076,7 +1076,9 @@ void Sensor::jsonToState(const QString &json) QDateTime dt = QDateTime::currentDateTime().addSecs(-120); if (map.contains("lastupdated")) { - QDateTime lu = QDateTime::fromString(map["lastupdated"].toString(), QLatin1String("yyyy-MM-ddTHH:mm:ss")); + QString lastupdated = map["lastupdated"].toString(); + QString format = lastupdated.length() == 19 ? QLatin1String("yyyy-MM-ddTHH:mm:ss") : QLatin1String("yyyy-MM-ddTHH:mm:ss.zzz"); + QDateTime lu = QDateTime::fromString(lastupdated, format); if (lu < dt) { dt = lu;