Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some more Hue lights #6936

Merged
merged 22 commits into from May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
209 changes: 131 additions & 78 deletions device_access_fn.cpp
Expand Up @@ -480,15 +480,19 @@ bool parseNumericToString(Resource *r, ResourceItem *item, const deCONZ::ApsData
/*! A generic function to parse ZCL values from read/report commands.
The item->parseParameters() is expected to be an object (given in the device description file).

{"fn": "zcl", "ep": endpoint, "cl": clusterId, "at": attributeId, "mf": manufacturerCode, "eval": expression}
{"fn": "zcl:attr", "ep": endpoint, "cl": clusterId, "mf": manufacturerCode, "at": attributeId, "eval": expression}

- endpoint: (optional) 255 means any endpoint, 0 means auto selected from the related resource, defaults to 0
- clusterId: string hex value
- manufacturerCode: (optional) string hex value
- attributeId: string hex value or array of string hex values
- manufacturerCode: (optional) string hex value, defaults to "0x0000" for non manufacturer specific commands
- expression: Javascript expression to transform the raw value
- expression: Javascript expression to transform the attribute value to the Item value

Example: { "parse": {"fn": "zcl:attr", "ep:" 1, "cl": "0x0402", "at": "0x0000", "eval": "Attr.val + R.item('config/offset').val" } }

TODO: move code to parse a ZCL command to separate function.

Example: { "parse": {"fn": "zcl", "ep:" 1, "cl": "0x0402", "at": "0x0000", "eval": "Attr.val + R.item('config/offset').val" } }
Exmaple: { "parse": {"fn": "zcl:cmd", "ep": 2, "cl": "0xfc00", "mf", "0x100b", "script": "fc00_buttonevent.js" } }
*/
bool parseZclAttribute(Resource *r, ResourceItem *item, const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclFrame, const QVariant &parseParameters)
{
Expand Down Expand Up @@ -1638,15 +1642,15 @@ bool parseAndSyncTime(Resource *r, ResourceItem *item, const deCONZ::ApsDataIndi
/*! A generic function to read ZCL attributes.
The item->readParameters() is expected to be an object (given in the device description file).

{ "fn": "zcl", "ep": endpoint, "cl" : clusterId, "at": attributeId, "mf": manufacturerCode, "noseq": noSequenceNumber }
{ "fn": "zcl:attr", "ep": endpoint, "cl" : clusterId, "mf": manufacturerCode, "at": attributeId, "noseq": noSequenceNumber }

- endpoint, 0xff means any endpoint
- endpoint: the destination endpoint, use 0 for auto endpoint (from the uniqueid)
- clusterId: string hex value
- attributeId: string hex value
- manufacturerCode: (optional) string hex value, defaults to "0x0000" for non manufacturer specific commands
- manufacturerCode: (optional) string hex value
- attributeId: string hex value or array of up to 8 string hex values
- noSequenceNumber: (optional) bool must be set to `true` and must only be present if needed

Example: { "read": {"fn": "zcl", "ep": 1, "cl": "0x0402", "at": "0x0000", "mf": "0x110b"} }
Example: { "read": {"fn": "zcl:attr", "ep": 1, "cl": "0x0402", "mf": "0x110b", "at": "0x0000"} }
*/
static DA_ReadResult readZclAttribute(const Resource *r, const ResourceItem *item, deCONZ::ApsController *apsCtrl, const QVariant &readParameters)
{
Expand Down Expand Up @@ -1701,16 +1705,16 @@ static DA_ReadResult readZclAttribute(const Resource *r, const ResourceItem *ite
/*! A generic function to write ZCL attributes.
The \p writeParameters is expected to contain one object (given in the device description file).

{ "fn": "zcl", "ep": endpoint, "cl": clusterId, "at": attributeId, "dt": zclDataType, "mf": manufacturerCode, "eval": expression }
{ "fn": "zcl:attr", "ep": endpoint, "cl": clusterId, "mf": manufacturerCode, "at": attributeId, "dt": zclDataType, "eval": expression }

- endpoint: (optional) the destination endpoint
- endpoint: the destination endpoint, use 0 for auto endpoint (from the uniqueid)
- clusterId: string hex value
- manufacturerCode: (optional) string hex value
- attributeId: string hex value
- zclDataType: string hex value
- manufacturerCode: must be set to 0x0000 for non manufacturer specific commands
- expression: to transform the item value
- expression: to transform the item value to the attribute value

Example: "write": {"cl": "0x0000", "at": "0xff0d", "dt": "0x20", "mf": "0x11F5", "eval": "Item.val"}
Example: "write": {"fn": "zcl:attr", "cl": "0x0000", "mf": "0x11F5", "at": "0xff0d", "dt": "0x20", "eval": "Item.val"}
*/
bool writeZclAttribute(const Resource *r, const ResourceItem *item, deCONZ::ApsController *apsCtrl, const QVariant &writeParameters)
{
Expand All @@ -1719,6 +1723,7 @@ bool writeZclAttribute(const Resource *r, const ResourceItem *item, deCONZ::ApsC
Q_ASSERT(apsCtrl);

bool result = false;

const auto rParent = r->parentResource() ? r->parentResource() : r;
const auto *extAddr = rParent->item(RAttrExtAddress);
const auto *nwkAddr = rParent->item(RAttrNwkAddress);
Expand Down Expand Up @@ -1759,97 +1764,141 @@ bool writeZclAttribute(const Resource *r, const ResourceItem *item, deCONZ::ApsC
bool ok;
const auto dataType = variantToUint(map.value("dt"), UINT8_MAX, &ok);
const auto expr = map.value("eval").toString();

if (!ok || expr.isEmpty())
{
return result;
}
deCONZ::ZclAttribute attribute(param.attributes[0], dataType, QLatin1String(""), deCONZ::ZclReadWrite, true);

DBG_Printf(DBG_INFO, "writeZclAttribute, ep: 0x%02X, cl: 0x%04X, attr: 0x%04X, type: 0x%02X, mfcode: 0x%04X, expr: %s\n", param.endpoint, param.clusterId, param.attributes.front(), dataType, param.manufacturerCode, qPrintable(expr));
DeviceJs &engine = *DeviceJs::instance();
engine.reset();
engine.setResource(r);
engine.setItem(item);
if (engine.evaluate(expr) == JsEvalResult::Ok)
{
const auto value = engine.result();
DBG_Printf(DBG_DDF, "%s/%s expression: %s --> %s\n", r->item(RAttrUniqueId)->toCString(), item->descriptor().suffix, qPrintable(expr), qPrintable(value.toString()));
attribute.setValue(value);
}
else
{
DBG_Printf(DBG_DDF, "failed to evaluate expression for %s/%s: %s, err: %s\n", qPrintable(r->item(RAttrUniqueId)->toString()), item->descriptor().suffix, qPrintable(expr), qPrintable(engine.errorString()));
return result;
}

deCONZ::ApsDataRequest req;
deCONZ::ZclFrame zclFrame;
const auto zclResult = ZCL_WriteAttribute(param, extAddr->toNumber(), nwkAddr->toNumber(), apsCtrl, &attribute);

req.setDstEndpoint(param.endpoint);
req.setTxOptions(deCONZ::ApsTxAcknowledgedTransmission);
req.setDstAddressMode(deCONZ::ApsNwkAddress);
req.dstAddress().setNwk(nwkAddr->toNumber());
req.dstAddress().setExt(extAddr->toNumber());
req.setClusterId(param.clusterId);
req.setProfileId(HA_PROFILE_ID);
req.setSrcEndpoint(1); // TODO
result = zclResult.isEnqueued;
return result;
}

zclFrame.setSequenceNumber(zclNextSequenceNumber());
zclFrame.setCommandId(deCONZ::ZclWriteAttributesId);
/*! A generic function to send a cluster-specific ZCL command.
The \p cmdParameters is expected to contain one object (given in the device description file).

{ "fn": "zcl:cmd", "ep": endpoint, "cl": clusterId, "mf": manufacturerCode, "cmd": commandId, "eval": expression }

- endpoint: the destination endpoint, use 0 for auto endpoint (from the uniqueid)
- clusterId: string hex value
- manufacturerCode: (optional) string hex value
- commandId: string hex value
- expression: (optional) to transform the item value to the command payload as hex string value

Example: "read": {"fn": "zcl:cmd", "ep": "0x0b", "cl": "0x0000", "mf": "0x100b", "cmd": "0xc0", "eval": "'002d00000040'"}
*/
static DA_ReadResult sendZclCommand(const Resource *r, const ResourceItem *item, deCONZ::ApsController *apsCtrl, const QVariant &cmdParameters)
{
Q_ASSERT(r);
Q_ASSERT(item);
Q_ASSERT(apsCtrl);

if (param.manufacturerCode)
DA_ReadResult result{};

const auto rParent = r->parentResource() ? r->parentResource() : r;
const auto *extAddr = rParent->item(RAttrExtAddress);
const auto *nwkAddr = rParent->item(RAttrNwkAddress);

if (!extAddr || !nwkAddr)
{
zclFrame.setFrameControl(deCONZ::ZclFCProfileCommand |
deCONZ::ZclFCManufacturerSpecific |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
zclFrame.setManufacturerCode(param.manufacturerCode);
return result;
}
else

const auto map = cmdParameters.toMap();
ZCL_Param param = getZclParam(map);

if (!param.valid)
{
zclFrame.setFrameControl(deCONZ::ZclFCProfileCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
return result;
}

{ // payload
deCONZ::ZclAttribute attribute(param.attributes[0], dataType, QLatin1String(""), deCONZ::ZclReadWrite, true);
std::vector<uint8_t> payload;

if (!expr.isEmpty())
if (map.contains("eval"))
{
const auto expr = map.value("eval").toString();
if (expr.isEmpty())
{
DeviceJs &engine = *DeviceJs::instance();
engine.reset();
engine.setResource(r);
engine.setItem(item);

if (engine.evaluate(expr) == JsEvalResult::Ok)
{
const auto res = engine.result();
DBG_Printf(DBG_DDF, "%s/%s expression: %s --> %s\n", r->item(RAttrUniqueId)->toCString(), item->descriptor().suffix, qPrintable(expr), qPrintable(res.toString()));
attribute.setValue(res);
}
else
return result;
}
DeviceJs &engine = *DeviceJs::instance();
engine.reset();
engine.setResource(r);
engine.setItem(item);
if (engine.evaluate(expr) == JsEvalResult::Ok)
{
const auto value = engine.result();
DBG_Printf(DBG_DDF, "%s/%s expression: %s --> %s\n", r->item(RAttrUniqueId)->toCString(), item->descriptor().suffix, qPrintable(expr), qPrintable(value.toString()));
auto a = QByteArray::fromHex(value.toString().toLatin1());
for (const auto b : a)
{
DBG_Printf(DBG_DDF, "failed to evaluate expression for %s/%s: %s, err: %s\n", qPrintable(r->item(RAttrUniqueId)->toString()), item->descriptor().suffix, qPrintable(expr), qPrintable(engine.errorString()));
return result;
payload.push_back(b);
}
}

QDataStream stream(&zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);

stream << attribute.id();
stream << attribute.dataType();

if (!attribute.writeToStream(stream))
else
{
DBG_Printf(DBG_DDF, "failed to evaluate expression for %s/%s: %s, err: %s\n", qPrintable(r->item(RAttrUniqueId)->toString()), item->descriptor().suffix, qPrintable(expr), qPrintable(engine.errorString()));
return result;
}
}

{ // ZCL frame
QDataStream stream(&req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
zclFrame.writeToStream(stream);
}
const auto zclResult = ZCL_SendCommand(param, extAddr->toNumber(), nwkAddr->toNumber(), apsCtrl, &payload);

result = apsCtrl->apsdeDataRequest(req) == deCONZ::Success;
result.isEnqueued = zclResult.isEnqueued;
result.apsReqId = zclResult.apsReqId;
result.sequenceNumber = zclResult.sequenceNumber;
result.clusterId = param.clusterId;
result.ignoreResponseSequenceNumber = param.ignoreResponseSeq == 1;

return result;
}

/*! A generic function to send a cluster-specific ZCL command.
The \p cmdParameters is expected to contain one object (given in the device description file).

{ "fn": "zcl:cmd", "ep": endpoint, "cl": clusterId, "mf": manufacturerCode, "cmd": commandId, "eval": expression }

- endpoint: the destination endpoint, use 0 for auto endpoint (from the uniqueid)
- clusterId: string hex value
- manufacturerCode: (optional) string hex value
- commandId: string hex value
- expression: (optional) to transform the item value to the command payload as hex string value

Example: "write": {"fn": "zcl:cmd", "cl": "0x0000", "mf": "0x100b", "cmd": "0xc0", "eval": "'002d00000040'"}
*/
bool writeZclCommand(const Resource *r, const ResourceItem *item, deCONZ::ApsController *apsCtrl, const QVariant &cmdParameters)
{
const auto result = sendZclCommand(r, item, apsCtrl, cmdParameters);
return result.isEnqueued;
}

ParseFunction_t DA_GetParseFunction(const QVariant &params)
{
ParseFunction_t result = nullptr;

const std::array<ParseFunction, 6> functions =
const std::array<ParseFunction, 8> functions =
{
ParseFunction(QLatin1String("zcl"), 1, parseZclAttribute),
ParseFunction(QLatin1String("zcl"), 1, parseZclAttribute), // Deprecated
ParseFunction(QLatin1String("zcl:attr"), 1, parseZclAttribute),
ParseFunction(QLatin1String("zcl:cmd"), 1, parseZclAttribute),
ParseFunction(QLatin1String("xiaomi:special"), 1, parseXiaomiSpecial),
ParseFunction(QLatin1String("ias:zonestatus"), 1, parseIasZoneNotificationAndStatus),
ParseFunction(QLatin1String("tuya"), 1, parseTuyaData),
Expand All @@ -1870,7 +1919,7 @@ ParseFunction_t DA_GetParseFunction(const QVariant &params)
}
else
{
fnName = QLatin1String("zcl"); // default
fnName = QLatin1String("zcl:attr"); // default
}
}

Expand All @@ -1890,9 +1939,11 @@ ReadFunction_t DA_GetReadFunction(const QVariant &params)
{
ReadFunction_t result = nullptr;

const std::array<ReadFunction, 2> functions =
const std::array<ReadFunction, 4> functions =
{
ReadFunction(QLatin1String("zcl"), 1, readZclAttribute),
ReadFunction(QLatin1String("zcl"), 1, readZclAttribute), // Deprecated
ReadFunction(QLatin1String("zcl:attr"), 1, readZclAttribute),
ReadFunction(QLatin1String("zcl:cmd"), 1, sendZclCommand),
ReadFunction(QLatin1String("tuya"), 1, readTuyaAllData)
};

Expand All @@ -1909,7 +1960,7 @@ ReadFunction_t DA_GetReadFunction(const QVariant &params)
}
else
{
fnName = QLatin1String("zcl"); // default
fnName = QLatin1String("zcl:attr"); // default
}
}

Expand All @@ -1929,9 +1980,11 @@ WriteFunction_t DA_GetWriteFunction(const QVariant &params)
{
WriteFunction_t result = nullptr;

const std::array<WriteFunction, 2> functions =
const std::array<WriteFunction, 4> functions =
{
WriteFunction(QLatin1String("zcl"), 1, writeZclAttribute),
WriteFunction(QLatin1String("zcl"), 1, writeZclAttribute), // Deprecated
WriteFunction(QLatin1String("zcl:attr"), 1, writeZclAttribute),
WriteFunction(QLatin1String("zcl:cmd"), 1, writeZclCommand),
WriteFunction(QLatin1String("tuya"), 1, writeTuyaData)
};

Expand All @@ -1948,7 +2001,7 @@ WriteFunction_t DA_GetWriteFunction(const QVariant &params)
}
else
{
fnName = QLatin1String("zcl"); // default
fnName = QLatin1String("zcl:attr"); // default
}
}

Expand Down
22 changes: 7 additions & 15 deletions devices/generic/items/attr_productname_item.json
@@ -1,16 +1,8 @@
{
"schema": "resourceitem1.schema.json",
"id": "attr/productname",
"datatype": "String",
"access": "R",
"public": true,
"description": "Product name of the device.",
"parse": {
"fn": "zcl", "ep": "0x0b", "cl": "0x0000", "mf": "0x100b", "at": "0x0040",
"eval": "Item.val = Attr.val"
},
"read": {
"fn": "zcl", "ep": "0x0b", "cl": "0x0000", "mf": "0x100b", "at": "0x0040"
},
"refresh.interval": 86400
}
"schema": "resourceitem1.schema.json",
"id": "attr/productname",
"datatype": "String",
"access": "R",
"public": true,
"description": "Product name of the device."
}
33 changes: 33 additions & 0 deletions devices/philips/0000_capabilities.js
@@ -0,0 +1,33 @@
/* global Item, ZclFrame */
/* eslint-disable no-var */

const status = ZclFrame.at(1) << 8 | ZclFrame.at(0)
const offset = ZclFrame.at(3) << 8 | ZclFrame.at(2)
// const totalLength = ZclFrame.at(7) << 8 | ZclFrame.at(6)
// const length = ZclFrame.at(10) // ostring length
var o = 11 // start of ostring data bytes
var s = ''

if (status === 0x0000) {
if (offset === 0x0000) {
if (ZclFrame.at(o++) === 0x0A) { // unknown
o += 10 // fixed L
if (ZclFrame.at(o++) === 0x0A) { // modelid
o += ZclFrame.at(o) + 1
if (ZclFrame.at(o++) === 0x12) { // manufacturername
o += ZclFrame.at(o) + 1
if (ZclFrame.at(o) === 0x42) { // productname
s = 'offset: 0x' + ('00000000' + (Number(o) - 11).toString(16)).slice(-8)
}
}
}
}
} else {
if (ZclFrame.at(o++) === 0x42) { // T: productname
for (var l = ZclFrame.at(o++); l > 0; l--) {
s += String.fromCharCode(ZclFrame.at(o++))
}
Item.val = s
}
}
}
1 change: 1 addition & 0 deletions devices/philips/fc03_state.js
@@ -1,4 +1,5 @@
/* global R, ZclFrame */
/* eslint-disable no-var */

const attrid = ZclFrame.at(1) << 8 | ZclFrame.at(0)
if (attrid === 0x0002) {
Expand Down