From 9c38d598a8a3587279d6439d02ae010c2fad0e07 Mon Sep 17 00:00:00 2001 From: ebaauw Date: Sun, 11 Mar 2018 15:14:53 +0100 Subject: [PATCH] Heiman Siren, see #404 --- database.cpp | 11 ------- de_web_plugin.cpp | 52 ++++++-------------------------- de_web_plugin_private.h | 7 ++++- light_node.cpp | 7 +++-- rest_lights.cpp | 66 ++++++++++++++++++++++++++++++++--------- zcl_tasks.cpp | 46 ++++++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 71 deletions(-) diff --git a/database.cpp b/database.cpp index f7b371bc7c..bbc21b04c9 100644 --- a/database.cpp +++ b/database.cpp @@ -2081,17 +2081,6 @@ static int sqliteLoadAllSensorsCallback(void *user, int ncols, char **colval , c item = sensor.addItem(DataTypeBool, RStateWater); item->setValue(false); } - else if (sensor.type().endsWith(QLatin1String("Warning"))) - { - if (sensor.fingerPrint().hasInCluster(IAS_WD_CLUSTER_ID)) - { - clusterId = IAS_WD_CLUSTER_ID; - } - item = sensor.addItem(DataTypeString, RStateAlert); - item->setValue(QString("none")); - item = sensor.addItem(DataTypeString, RStateEffect); - item->setValue(QString("none")); - } else if (sensor.type().endsWith(QLatin1String("Consumption"))) { if (sensor.fingerPrint().hasInCluster(METERING_CLUSTER_ID)) diff --git a/de_web_plugin.cpp b/de_web_plugin.cpp index 51f35a9292..f15dc06131 100644 --- a/de_web_plugin.cpp +++ b/de_web_plugin.cpp @@ -1194,6 +1194,12 @@ void DeRestPluginPrivate::addLightNode(const deCONZ::Node *node) } break; + case DEV_ID_IAS_WARNING_DEVICE: + { + lightNode.setHaEndpoint(*i); + } + break; + default: { } @@ -1519,6 +1525,7 @@ LightNode *DeRestPluginPrivate::updateLightNode(const deCONZ::NodeEvent &event) case DEV_ID_ZLL_ONOFF_PLUGIN_UNIT: case DEV_ID_Z30_ONOFF_PLUGIN_UNIT: case DEV_ID_ZLL_ONOFF_SENSOR: + case DEV_ID_IAS_WARNING_DEVICE: break; default: @@ -2557,7 +2564,6 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const deCONZ:: SensorFingerprint fpSwitch; SensorFingerprint fpTemperatureSensor; SensorFingerprint fpWaterSensor; - SensorFingerprint fpWarningSensor; { // scan client clusters of endpoint QList::const_iterator ci = i->outClusters().constBegin(); @@ -2662,7 +2668,6 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const deCONZ:: fpSwitch.inClusters.push_back(ci->id()); fpTemperatureSensor.inClusters.push_back(ci->id()); fpWaterSensor.inClusters.push_back(ci->id()); - fpWarningSensor.inClusters.push_back(ci->id()); // } } break; @@ -2775,13 +2780,6 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const deCONZ:: } break; - case IAS_WD_CLUSTER_ID: - if (modelId == QLatin1String("WarningDevice")) // Heiman siren - { - fpWarningSensor.inClusters.push_back(ci->id()); - } - break; - case OCCUPANCY_SENSING_CLUSTER_ID: { // @manup: Does this sensor indeed have an OCCUPANCY_SENSING_CLUSTER_ID custer? @@ -2949,7 +2947,6 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const deCONZ:: // ZHAPresence if (fpPresenceSensor.hasInCluster(OCCUPANCY_SENSING_CLUSTER_ID) || fpPresenceSensor.hasInCluster(IAS_ZONE_CLUSTER_ID) || - fpPresenceSensor.hasInCluster(IAS_WD_CLUSTER_ID) || fpPresenceSensor.hasOutCluster(ONOFF_CLUSTER_ID)) { fpPresenceSensor.endpoint = i->endpoint(); @@ -3112,24 +3109,6 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const deCONZ:: } } - // ZHAWarning - if (fpWarningSensor.hasInCluster(IAS_WD_CLUSTER_ID)) - { - fpWarningSensor.endpoint = i->endpoint(); - fpWarningSensor.deviceId = i->deviceId(); - fpWarningSensor.profileId = i->profileId(); - - sensor = getSensorNodeForFingerPrint(node->address().ext(), fpWarningSensor, "ZHAWarning"); - if (!sensor || sensor->deletedState() != Sensor::StateNormal) - { - addSensorNode(node, fpWarningSensor, "ZHAWarning", modelId, manufacturer); - } - else - { - checkSensorNodeReachable(sensor); - } - } - // ZHAConsumption if (fpConsumptionSensor.hasInCluster(METERING_CLUSTER_ID) || fpConsumptionSensor.hasInCluster(ANALOG_INPUT_CLUSTER_ID)) @@ -3285,10 +3264,6 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const SensorFi { clusterId = OCCUPANCY_SENSING_CLUSTER_ID; } - else if (sensorNode.fingerPrint().hasInCluster(IAS_WD_CLUSTER_ID)) - { - clusterId = IAS_WD_CLUSTER_ID; - } else if (sensorNode.fingerPrint().hasInCluster(IAS_ZONE_CLUSTER_ID)) { clusterId = IAS_ZONE_CLUSTER_ID; @@ -3351,17 +3326,6 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const SensorFi item = sensorNode.addItem(DataTypeBool, RStateWater); item->setValue(false); } - else if (sensorNode.type().endsWith(QLatin1String("Warning"))) - { - if (sensorNode.fingerPrint().hasInCluster(IAS_WD_CLUSTER_ID)) - { - clusterId = IAS_WD_CLUSTER_ID; - } - item = sensorNode.addItem(DataTypeString, RStateAlert); - item->setValue(QString("none")); - item = sensorNode.addItem(DataTypeString, RStateEffect); - item->setValue(QString("none")); - } else if (sensorNode.type().endsWith(QLatin1String("Consumption"))) { if (sensorNode.fingerPrint().hasInCluster(METERING_CLUSTER_ID)) @@ -12593,6 +12557,8 @@ void DeRestPluginPrivate::pushSensorInfoToCore(Sensor *sensor) { } // use name from light else if (sensor->modelId() == QLatin1String("SML001") && sensor->type() != QLatin1String("ZHAPresence")) { } // use name from ZHAPresence sensor only + else if (sensor->modelId() == QLatin1String("WarningDevice") && sensor->type() == QLatin1String("ZHAAlarm")) + { } // use name from light else if (!sensor->name().isEmpty()) { q->nodeUpdated(sensor->address().ext(), QLatin1String("name"), sensor->name()); diff --git a/de_web_plugin_private.h b/de_web_plugin_private.h index 29f66261ff..06fe8aef45 100644 --- a/de_web_plugin_private.h +++ b/de_web_plugin_private.h @@ -104,6 +104,7 @@ // #define DEV_ID_IAS_ZONE 0x0402 // IAS Zone +#define DEV_ID_IAS_WARNING_DEVICE 0x0403 // IAS Warning Device // Smart Energy devices #define DEV_ID_SE_METERING_DEVICE 0x0501 // Smart Energy metering device @@ -466,7 +467,8 @@ enum TaskType TaskAddToGroup = 30, TaskRemoveFromGroup = 31, TaskViewGroup = 32, - TaskTriggerEffect = 33 + TaskTriggerEffect = 33, + TaskWarning = 34 }; struct TaskItem @@ -501,6 +503,8 @@ struct TaskItem qreal hueReal; uint16_t identifyTime; uint8_t effectIdentifier; + uint8_t options; + uint16_t duration; uint8_t hue; uint8_t sat; uint8_t level; @@ -1029,6 +1033,7 @@ public Q_SLOTS: bool addTaskSetColorLoop(TaskItem &task, bool colorLoopActive, uint8_t speed); bool addTaskIdentify(TaskItem &task, uint16_t identifyTime); bool addTaskTriggerEffect(TaskItem &task, uint8_t effectIdentifier); + bool addTaskWarning(TaskItem &task, uint8_t options, uint16_t duration); bool addTaskAddToGroup(TaskItem &task, uint16_t groupId); bool addTaskViewGroup(TaskItem &task, uint16_t groupId); bool addTaskRemoveFromGroup(TaskItem &task, uint16_t groupId); diff --git a/light_node.cpp b/light_node.cpp index 8ff4d7a94b..f181aab93c 100644 --- a/light_node.cpp +++ b/light_node.cpp @@ -96,13 +96,14 @@ void LightNode::setManufacturerCode(uint16_t code) case VENDOR_IKEA: m_manufacturer = QLatin1String("IKEA of Sweden"); break; case VENDOR_INNR: m_manufacturer = QLatin1String("innr"); break; case VENDOR_INNR2: m_manufacturer = QLatin1String("innr"); break; - case VENDOR_INSTA: m_manufacturer = QLatin1String("Insta"); break; + case VENDOR_INSTA: m_manufacturer = QLatin1String("Insta"); break; case VENDOR_PHILIPS: m_manufacturer = QLatin1String("Philips"); break; case VENDOR_OSRAM_STACK: // fall through case VENDOR_OSRAM: m_manufacturer = QLatin1String("OSRAM"); break; case VENDOR_UBISYS: m_manufacturer = QLatin1String("ubisys"); break; case VENDOR_BUSCH_JAEGER: m_manufacturer = QLatin1String("Busch-Jaeger"); break; - case VENDOR_EMBER: m_manufacturer = QLatin1String("Heiman"); break; + case VENDOR_EMBER: // fall through + case VENDOR_120B: m_manufacturer = QLatin1String("Heiman"); break; default: m_manufacturer = QLatin1String("Unknown"); break; @@ -563,6 +564,8 @@ void LightNode::setHaEndpoint(const deCONZ::SimpleDescriptor &endpoint) case DEV_ID_Z30_COLOR_TEMPERATURE_LIGHT: ltype = QLatin1String("Color temperature light"); break; case DEV_ID_ZLL_COLOR_TEMPERATURE_LIGHT: ltype = QLatin1String("Color temperature light"); break; case DEV_ID_XIAOMI_SMART_PLUG: ltype = QLatin1String("Smart plug"); break; + case DEV_ID_IAS_WARNING_DEVICE: removeItem(RStateOn); + ltype = QLatin1String("Warning device"); break; default: break; } diff --git a/rest_lights.cpp b/rest_lights.cpp index d34942e760..f40b673d49 100644 --- a/rest_lights.cpp +++ b/rest_lights.cpp @@ -987,48 +987,85 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) TaskItem task; copyTaskReq(taskRef, task); QString alert = map["alert"].toString(); + bool isWarningDevice = taskRef.lightNode->type() == QLatin1String("Warning device"); if (alert == "none") { - task.taskType = TaskIdentify; - task.identifyTime = 0; + if (isWarningDevice) + { + task.taskType = TaskWarning; + task.options = 0x00; // Warning mode 0 (no warning), No strobe + task.duration = 0; + } + else + { + task.taskType = TaskIdentify; + task.identifyTime = 0; + } } else if (alert == "select") { - task.taskType = TaskIdentify; - task.identifyTime = 2; // Hue lights don't react to 1. + if (isWarningDevice) + { + task.taskType = TaskWarning; + task.options = 0x14; // Warning mode 1 (burglar), Strobe + task.duration = 1; + } + else + { + task.taskType = TaskIdentify; + task.identifyTime = 2; // Hue lights don't react to 1. + } } else if (alert == "lselect") { - task.taskType = TaskIdentify; - task.identifyTime = 15; // Default for Philips Hue bridge + if (isWarningDevice) + { + task.taskType = TaskWarning; + task.options = 0x14; // Warning mode 1 (burglar), Strobe + task.duration = 300; + } + else + { + task.taskType = TaskIdentify; + task.identifyTime = 15; // Default for Philips Hue bridge + } } else if (alert == "blink") { - task.taskType = TaskTriggerEffect; - task.effectIdentifier = 0x00; + if (isWarningDevice) + { + task.taskType = TaskWarning; + task.options = 0x04; // Warning mode 0 (no warning), Strobe + task.duration = 300; + } + else + { + task.taskType = TaskTriggerEffect; + task.effectIdentifier = 0x00; + } } - else if (alert == "breathe") + else if (alert == "breathe" && !isWarningDevice) { task.taskType = TaskTriggerEffect; task.effectIdentifier = 0x01; } - else if (alert == "okay") + else if (alert == "okay" && !isWarningDevice) { task.taskType = TaskTriggerEffect; task.effectIdentifier = 0x02; } - else if (alert == "channelchange") + else if (alert == "channelchange" && !isWarningDevice) { task.taskType = TaskTriggerEffect; task.effectIdentifier = 0x0b; } - else if (alert == "finish") + else if (alert == "finish" && !isWarningDevice) { task.taskType = TaskTriggerEffect; task.effectIdentifier = 0xfe; } - else if (alert == "stop") + else if (alert == "stop" && !isWarningDevice) { task.taskType = TaskTriggerEffect; task.effectIdentifier = 0xff; @@ -1043,7 +1080,8 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp) taskToLocalData(task); if ((task.taskType == TaskIdentify && addTaskIdentify(task, task.identifyTime)) || - (task.taskType == TaskTriggerEffect && addTaskTriggerEffect(task, task.effectIdentifier))) + (task.taskType == TaskTriggerEffect && addTaskTriggerEffect(task, task.effectIdentifier)) || + (task.taskType == TaskWarning && addTaskWarning(task, task.options, task.duration))) { QVariantMap rspItem; QVariantMap rspItemState; diff --git a/zcl_tasks.cpp b/zcl_tasks.cpp index 6a75e7dc40..2b85ba51c5 100644 --- a/zcl_tasks.cpp +++ b/zcl_tasks.cpp @@ -781,6 +781,52 @@ bool DeRestPluginPrivate::addTaskTriggerEffect(TaskItem &task, uint8_t effectIde return addTask(task); } +/*! Add a warning task to the queue. + + \param task - the task item + \param options - the options + \param duration - the duration + \return true - on success + false - on error + */ +bool DeRestPluginPrivate::addTaskWarning(TaskItem &task, uint8_t options, uint16_t duration) +{ + task.taskType = TaskWarning; + task.options = options; + task.duration = duration; + uint8_t strobe_duty_cycle = 10; + uint8_t strobe_level = 0; + + task.req.setClusterId(IAS_WD_CLUSTER_ID); + task.req.setProfileId(HA_PROFILE_ID); + + task.zclFrame.payload().clear(); + task.zclFrame.setSequenceNumber(zclSeq++); + task.zclFrame.setCommandId(0x00); // Start Warning + task.zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand | + deCONZ::ZclFCDirectionClientToServer | + deCONZ::ZclFCDisableDefaultResponse); + + { // payload + QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + + stream << task.options; + stream << task.duration; + stream << strobe_duty_cycle; + stream << strobe_level; + } + + { // ZCL frame + task.req.asdu().clear(); // cleanup old request data if there is any + QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + task.zclFrame.writeToStream(stream); + } + + return addTask(task); +} + /*! Add a add to group task to the queue. \param task - the task item