Skip to content

Commit

Permalink
Support Samjin Multipurpose sensor (2)
Browse files Browse the repository at this point in the history
This creates three resources for ZHAOpenClose, ZHATemperature and ZHAVibration.

Issue #1311
  • Loading branch information
manup committed May 9, 2019
1 parent 3a50c7c commit 83b9e9a
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 9 deletions.
49 changes: 49 additions & 0 deletions bindings.cpp
Expand Up @@ -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<Sensor*>(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<Sensor*>(bt.restNode);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
25 changes: 21 additions & 4 deletions database.cpp
Expand Up @@ -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")))
Expand All @@ -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")))
{
Expand Down Expand Up @@ -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))
Expand Down
134 changes: 130 additions & 4 deletions de_web_plugin.cpp
Expand Up @@ -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());
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions de_web_plugin_private.h
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion rest_sensors.cpp
Expand Up @@ -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())
Expand Down

0 comments on commit 83b9e9a

Please sign in to comment.