diff --git a/bindings.cpp b/bindings.cpp index 2bdcd70895..384af89b08 100644 --- a/bindings.cpp +++ b/bindings.cpp @@ -1279,6 +1279,43 @@ bool DeRestPluginPrivate::sendConfigureReportingRequest(BindingTask &bt) return sendConfigureReportingRequest(bt, {rq, rq2, rq3, rq4}); } + else if (bt.binding.clusterId == SAMJIN_CLUSTER_ID && checkMacVendor(bt.restNode->address(), VENDOR_SAMJIN)) + { + Sensor *sensor = dynamic_cast(bt.restNode); + if (!sensor) + { + return false; + } + + // based on https://github.com/SmartThingsCommunity/SmartThingsPublic/blob/master/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy + if (sensor->type() == QLatin1String("ZHAVibration") && sensor->modelId() == QLatin1String("multi")) + { + rq.dataType = deCONZ::Zcl16BitInt; + rq.attributeId = 0x0012; // acceleration x + rq.minInterval = 1; + rq.maxInterval = 3600; + rq.reportableChange16bit = 1; + rq.manufacturerCode = VENDOR_SAMJIN; + + ConfigureReportingRequest rq2; + rq2.dataType = deCONZ::Zcl16BitInt; + rq2.attributeId = 0x0013; // acceleration y + rq2.minInterval = 1; + rq2.maxInterval = 3600; + rq2.reportableChange16bit = 1; + rq2.manufacturerCode = VENDOR_SAMJIN; + + ConfigureReportingRequest rq3; + rq3.dataType = deCONZ::Zcl16BitInt; + rq3.attributeId = 0x0014; // acceleration z + rq3.minInterval = 1; + rq3.maxInterval = 3600; + rq3.reportableChange16bit = 1; + rq3.manufacturerCode = VENDOR_SAMJIN; + + return sendConfigureReportingRequest(bt, {rq, rq2, rq3}); + } + } else if (bt.binding.clusterId == BASIC_CLUSTER_ID && checkMacVendor(bt.restNode->address(), VENDOR_PHILIPS)) { Sensor *sensor = dynamic_cast(bt.restNode); @@ -1841,6 +1878,17 @@ bool DeRestPluginPrivate::checkSensorBindingsForAttributeReporting(Sensor *senso { val = sensor->getZclValue(*i, 0x0000); // Local temperature } + else if (*i == SAMJIN_CLUSTER_ID) + { + if (sensor->modelId() == QLatin1String("multi")) + { + val = sensor->getZclValue(*i, 0x0012); // Acceleration X + } + else + { + continue; + } + } quint16 maxInterval = (val.maxInterval > 0) ? (val.maxInterval * 3 / 2) : (60 * 45); @@ -1891,6 +1939,7 @@ bool DeRestPluginPrivate::checkSensorBindingsForAttributeReporting(Sensor *senso case BASIC_CLUSTER_ID: case BINARY_INPUT_CLUSTER_ID: case THERMOSTAT_CLUSTER_ID: + case SAMJIN_CLUSTER_ID: { DBG_Printf(DBG_INFO_L2, "0x%016llX (%s) create binding for attribute reporting of cluster 0x%04X on endpoint 0x%02X\n", sensor->address().ext(), qPrintable(sensor->modelId()), (*i), srcEndpoint); diff --git a/database.cpp b/database.cpp index 595edd53e3..b50700b26f 100644 --- a/database.cpp +++ b/database.cpp @@ -3060,6 +3060,10 @@ static int sqliteLoadAllSensorsCallback(void *user, int ncols, char **colval , c { clusterId = clusterId ? clusterId : DOOR_LOCK_CLUSTER_ID; } + else if (sensor.fingerPrint().hasInCluster(SAMJIN_CLUSTER_ID)) + { + clusterId = clusterId ? clusterId : SAMJIN_CLUSTER_ID; + } item = sensor.addItem(DataTypeBool, RStateVibration); item->setValue(false); if (sensor.modelId().startsWith(QLatin1String("lumi.vibration"))) @@ -3070,6 +3074,12 @@ static int sqliteLoadAllSensorsCallback(void *user, int ncols, char **colval , c item = sensor.addItem(DataTypeUInt16, RStateTiltAngle); item = sensor.addItem(DataTypeUInt16, RStateVibrationStrength); } + else if (sensor.modelId() == QLatin1String("multi") && sensor.manufacturer() == QLatin1String("Samjin")) + { + item = sensor.addItem(DataTypeInt16, RStateOrientationX); + item = sensor.addItem(DataTypeInt16, RStateOrientationY); + item = sensor.addItem(DataTypeInt16, RStateOrientationZ); + } } else if (sensor.type().endsWith(QLatin1String("Water"))) { @@ -3255,10 +3265,17 @@ static int sqliteLoadAllSensorsCallback(void *user, int ncols, char **colval , c if (sensor.fingerPrint().hasInCluster(IAS_ZONE_CLUSTER_ID)) { - item = sensor.addItem(DataTypeBool, RStateLowBattery); - item->setValue(false); - item = sensor.addItem(DataTypeBool, RStateTampered); - item->setValue(false); + if (sensor.modelId() == QLatin1String("multi") && sensor.manufacturer() == QLatin1String("Samjin")) + { + // no support for some IAS Zone flags + } + else + { + item = sensor.addItem(DataTypeBool, RStateLowBattery); + item->setValue(false); + item = sensor.addItem(DataTypeBool, RStateTampered); + item->setValue(false); + } } if (sensor.fingerPrint().hasInCluster(POWER_CONFIGURATION_CLUSTER_ID)) diff --git a/de_web_plugin.cpp b/de_web_plugin.cpp index 8fc970139c..f3680f6ed5 100644 --- a/de_web_plugin.cpp +++ b/de_web_plugin.cpp @@ -3667,6 +3667,15 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const deCONZ:: } break; + case SAMJIN_CLUSTER_ID: + { + if (modelId == QLatin1String("multi")) // Samjin Multipurpose sensor + { + fpVibrationSensor.inClusters.push_back(SAMJIN_CLUSTER_ID); + } + } + break; + case METERING_CLUSTER_ID: { fpConsumptionSensor.inClusters.push_back(ci->id()); @@ -3981,6 +3990,7 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const deCONZ:: // ZHAVibration if (fpVibrationSensor.hasInCluster(IAS_ZONE_CLUSTER_ID) || + fpVibrationSensor.hasInCluster(SAMJIN_CLUSTER_ID) || fpVibrationSensor.hasInCluster(DOOR_LOCK_CLUSTER_ID)) { fpVibrationSensor.endpoint = i->endpoint(); @@ -4300,6 +4310,10 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const SensorFi { clusterId = DOOR_LOCK_CLUSTER_ID; } + else if (sensorNode.fingerPrint().hasInCluster(SAMJIN_CLUSTER_ID)) + { + clusterId = SAMJIN_CLUSTER_ID; + } item = sensorNode.addItem(DataTypeBool, RStateVibration); item->setValue(false); } @@ -4596,6 +4610,13 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const SensorFi else if (node->nodeDescriptor().manufacturerCode() == VENDOR_SAMJIN) { sensorNode.setManufacturer("Samjin"); + + if (sensorNode.type() == QLatin1String("ZHAVibration")) + { + item = sensorNode.addItem(DataTypeInt16, RStateOrientationX); + item = sensorNode.addItem(DataTypeInt16, RStateOrientationY); + item = sensorNode.addItem(DataTypeInt16, RStateOrientationZ); + } } else if (node->nodeDescriptor().manufacturerCode() == VENDOR_INNR) { @@ -4626,10 +4647,17 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const SensorFi if (clusterId == IAS_ZONE_CLUSTER_ID) { - item = sensorNode.addItem(DataTypeBool, RStateLowBattery); - item->setValue(false); - item = sensorNode.addItem(DataTypeBool, RStateTampered); - item->setValue(false); + if (modelId == QLatin1String("multi") && sensorNode.manufacturer() == QLatin1String("Samjin")) + { + // no support for some IAS flags + } + else + { + item = sensorNode.addItem(DataTypeBool, RStateLowBattery); + item->setValue(false); + item = sensorNode.addItem(DataTypeBool, RStateTampered); + item->setValue(false); + } } QString uid = generateUniqueId(sensorNode.address().ext(), sensorNode.fingerPrint().endpoint, clusterId); @@ -5062,6 +5090,7 @@ void DeRestPluginPrivate::updateSensorNode(const deCONZ::NodeEvent &event) case METERING_CLUSTER_ID: case ELECTRICAL_MEASUREMENT_CLUSTER_ID: case DOOR_LOCK_CLUSTER_ID: + case SAMJIN_CLUSTER_ID: break; case VENDOR_CLUSTER_ID: @@ -5663,6 +5692,102 @@ void DeRestPluginPrivate::updateSensorNode(const deCONZ::NodeEvent &event) } } } + else if (event.clusterId() == SAMJIN_CLUSTER_ID) + { + for (;ia != enda; ++ia) + { + if (std::find(event.attributeIds().begin(), + event.attributeIds().end(), + ia->id()) == event.attributeIds().end()) + { + 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 + { + if (updateType != NodeValue::UpdateInvalid) + { + i->setZclValue(updateType, event.clusterId(), ia->id(), ia->numericValue()); + pushZclValueDb(event.node()->address().ext(), event.endpoint(), event.clusterId(), ia->id(), ia->numericValue().s16); + } + + ResourceItem *item = i->item(RStateOrientationX); + + if (item) + { + item->setValue(ia->numericValue().s16); + + if (item->lastSet() == item->lastChanged()) + { + Event e(RSensors, item->descriptor().suffix, i->id(), item); + enqueueEvent(e); + } + i->setNeedSaveDatabase(true); + i->updateStateTimestamp(); + enqueueEvent(Event(RSensors, RStateLastUpdated, i->id())); + } + } + else if (ia->id() == 0x0013) // accelerate y + { + if (updateType != NodeValue::UpdateInvalid) + { + i->setZclValue(updateType, event.clusterId(), ia->id(), ia->numericValue()); + pushZclValueDb(event.node()->address().ext(), event.endpoint(), event.clusterId(), ia->id(), ia->numericValue().s16); + } + + ResourceItem *item = i->item(RStateOrientationY); + + if (item) + { + item->setValue(ia->numericValue().s16); + + if (item->lastSet() == item->lastChanged()) + { + Event e(RSensors, item->descriptor().suffix, i->id(), item); + enqueueEvent(e); + } + i->setNeedSaveDatabase(true); + i->updateStateTimestamp(); + enqueueEvent(Event(RSensors, RStateLastUpdated, i->id())); + } + } + else if (ia->id() == 0x0014) // accelerate z + { + if (updateType != NodeValue::UpdateInvalid) + { + i->setZclValue(updateType, event.clusterId(), ia->id(), ia->numericValue()); + pushZclValueDb(event.node()->address().ext(), event.endpoint(), event.clusterId(), ia->id(), ia->numericValue().s16); + } + + ResourceItem *item = i->item(RStateOrientationZ); + + if (item) + { + item->setValue(ia->numericValue().s16); + + if (item->lastSet() == item->lastChanged()) + { + Event e(RSensors, item->descriptor().suffix, i->id(), item); + enqueueEvent(e); + } + i->setNeedSaveDatabase(true); + i->updateStateTimestamp(); + enqueueEvent(Event(RSensors, RStateLastUpdated, i->id())); + } + } + } + } else if (event.clusterId() == BASIC_CLUSTER_ID) { DBG_Printf(DBG_INFO_L2, "Update Sensor 0x%016llX Basic Cluster\n", event.node()->address().ext()); @@ -9913,6 +10038,7 @@ void DeRestPluginPrivate::nodeEvent(const deCONZ::NodeEvent &event) case VENDOR_CLUSTER_ID: case WINDOW_COVERING_CLUSTER_ID: case DOOR_LOCK_CLUSTER_ID: + case SAMJIN_CLUSTER_ID: { addSensorNode(event.node(), &event); updateSensorNode(event); diff --git a/de_web_plugin_private.h b/de_web_plugin_private.h index 1b72394a14..c1328eb350 100644 --- a/de_web_plugin_private.h +++ b/de_web_plugin_private.h @@ -182,6 +182,7 @@ #define DE_CLUSTER_ID 0xFC00 #define VENDOR_CLUSTER_ID 0xFC00 #define UBISYS_DEVICE_SETUP_CLUSTER_ID 0xFC00 +#define SAMJIN_CLUSTER_ID 0xFC02 #define XAL_CLUSTER_ID 0xFCCE #define IAS_ZONE_CLUSTER_ATTR_ZONE_STATUS_ID 0x0002 @@ -367,6 +368,7 @@ extern const quint64 osramMacPrefix; extern const quint64 philipsMacPrefix; extern const quint64 sinopeMacPrefix; extern const quint64 stMacPrefix; +extern const quint64 samjinMacPrefix; extern const quint64 tiMacPrefix; extern const quint64 ubisysMacPrefix; extern const quint64 xalMacPrefix; @@ -433,6 +435,8 @@ inline bool checkMacVendor(quint64 addr, quint16 vendor) return prefix == emberMacPrefix; case VENDOR_XAL: return prefix == xalMacPrefix; + case VENDOR_SAMJIN: + return prefix == samjinMacPrefix; default: return false; } diff --git a/rest_sensors.cpp b/rest_sensors.cpp index a7561eed9e..99ebad6994 100644 --- a/rest_sensors.cpp +++ b/rest_sensors.cpp @@ -2526,7 +2526,7 @@ void DeRestPluginPrivate::checkSensorStateTimerFired() enqueueEvent(Event(RSensors, RStateLastUpdated, sensor->id())); } } - else if (!item && sensor->modelId() == QLatin1String("lumi.vibration.aq1") && sensor->type() == QLatin1String("ZHAVibration")) + else if (!item && (sensor->modelId() == QLatin1String("lumi.vibration.aq1") || sensor->modelId() == QLatin1String("multi")) && sensor->type() == QLatin1String("ZHAVibration")) { item = sensor->item(RStateVibration); if (item && item->toBool())