Finish tradfri remote

master
Simon Stürz 2020-12-11 18:07:11 +01:00
parent 3b41efc12c
commit 616385b281
3 changed files with 165 additions and 101 deletions

View File

@ -472,7 +472,7 @@ void IntegrationPluginZigbeeLumi::setupThing(ThingSetupInfo *info)
ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster<ZigbeeClusterOnOff>(ZigbeeClusterLibrary::ClusterIdOnOff); ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster<ZigbeeClusterOnOff>(ZigbeeClusterLibrary::ClusterIdOnOff);
if (onOffCluster) { if (onOffCluster) {
connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [this, thing](bool power){ connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [this, thing](bool power){
qCDebug(dcZigbeeLumi()) << thing << "state changed" << (power ? "closed" : "open"); qCDebug(dcZigbeeLumi()) << thing << "state changed" << (power ? "pressed" : "released");
if (!power) { if (!power) {
emitEvent(Event(lumiButtonSensorPressedEventTypeId, thing->id())); emitEvent(Event(lumiButtonSensorPressedEventTypeId, thing->id()));
} }

View File

@ -31,6 +31,7 @@
#include "integrationpluginzigbeetradfri.h" #include "integrationpluginzigbeetradfri.h"
#include "plugininfo.h" #include "plugininfo.h"
#include "zigbeeutils.h"
#include "hardware/zigbee/zigbeehardwareresource.h" #include "hardware/zigbee/zigbeehardwareresource.h"
@ -86,7 +87,8 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n
bool handled = false; bool handled = false;
foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) {
if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceNonColourController) { if (endpoint->modelIdentifier().contains("on/off switch")) {
// "TRADFRI on/off switch"
qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI on/off switch" << node << endpoint; qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI on/off switch" << node << endpoint;
createThing(onOffSwitchThingClassId, networkUuid, node, endpoint); createThing(onOffSwitchThingClassId, networkUuid, node, endpoint);
initOnOffSwitch(node, endpoint); initOnOffSwitch(node, endpoint);
@ -100,15 +102,16 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n
handled = true; handled = true;
} }
if (endpoint->profile() == Zigbee::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDeviceNonColourSceneController) { if (endpoint->modelIdentifier().contains("remote control")) {
qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI remote control" << node << endpoint; qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI remote control" << node << endpoint;
createThing(remoteThingClassId, networkUuid, node, endpoint); createThing(remoteThingClassId, networkUuid, node, endpoint);
initRemote(node, endpoint); initRemote(node, endpoint);
handled = true; handled = true;
} }
if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceRemoteControl) { if (endpoint->modelIdentifier().contains("SYMFONISK")) {
qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI Symfonsik sound remote" << node << endpoint; // "SYMFONISK Sound Controller"
qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI Symfonisk sound remote" << node << endpoint;
createThing(soundRemoteThingClassId, networkUuid, node, endpoint); createThing(soundRemoteThingClassId, networkUuid, node, endpoint);
initOnOffSwitch(node, endpoint); initOnOffSwitch(node, endpoint);
handled = true; handled = true;
@ -310,13 +313,94 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info)
qCWarning(dcZigbeeTradfri()) << "Could not find on/off client cluster on" << thing << endpoint; qCWarning(dcZigbeeTradfri()) << "Could not find on/off client cluster on" << thing << endpoint;
} else { } else {
connect(onOffCluster, &ZigbeeClusterOnOff::commandSent, thing, [=](ZigbeeClusterOnOff::Command command){ connect(onOffCluster, &ZigbeeClusterOnOff::commandSent, thing, [=](ZigbeeClusterOnOff::Command command){
qCDebug(dcZigbeeTradfri()) << thing << "button pressed" << command; qCDebug(dcZigbeeTradfri()) << thing << "power command received" << command;
if (command == ZigbeeClusterOnOff::CommandToggle) { if (command == ZigbeeClusterOnOff::CommandToggle) {
qCDebug(dcZigbeeTradfri()) << thing << "pressed power"; qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Power";
emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Power"))); emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Power")));
} }
}); });
} }
// Receive level control commands
ZigbeeClusterLevelControl *levelCluster = endpoint->outputCluster<ZigbeeClusterLevelControl>(ZigbeeClusterLibrary::ClusterIdLevelControl);
if (!levelCluster) {
qCWarning(dcZigbeeTradfri()) << "Could not find level client cluster on" << thing << endpoint;
} else {
connect(levelCluster, &ZigbeeClusterLevelControl::commandSent, thing, [=](ZigbeeClusterLevelControl::Command command, const QByteArray &payload){
qCDebug(dcZigbeeTradfri()) << thing << "level command received" << command << payload.toHex();
switch (command) {
case ZigbeeClusterLevelControl::CommandMoveWithOnOff:
case ZigbeeClusterLevelControl::CommandStepWithOnOff:
qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Up";
emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Up")));
break;
default:
break;
}
});
connect(levelCluster, &ZigbeeClusterLevelControl::commandMoveSent, thing, [=](ZigbeeClusterLevelControl::MoveMode moveMode, quint8 rate){
qCDebug(dcZigbeeTradfri()) << "level command move received" << moveMode << rate;
switch (moveMode) {
case ZigbeeClusterLevelControl::MoveModeUp:
qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Up";
emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Up")));
break;
case ZigbeeClusterLevelControl::MoveModeDown:
qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Down";
emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Down")));
break;
}
});
connect(levelCluster, &ZigbeeClusterLevelControl::commandStepSent, thing, [=](ZigbeeClusterLevelControl::FadeMode fadeMode, quint8 stepSize, quint16 transitionTime){
qCDebug(dcZigbeeTradfri()) << "level command step received" << fadeMode << stepSize << transitionTime;
switch (fadeMode) {
case ZigbeeClusterLevelControl::FadeModeUp:
qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Up";
emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Up")));
break;
case ZigbeeClusterLevelControl::FadeModeDown:
qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Down";
emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Down")));
break;
}
});
}
// Receive scene commands
ZigbeeClusterScenes *scenesCluster = endpoint->outputCluster<ZigbeeClusterScenes>(ZigbeeClusterLibrary::ClusterIdScenes);
if (!scenesCluster) {
qCWarning(dcZigbeeTradfri()) << "Could not find scenes client cluster on" << thing << endpoint;
} else {
connect(scenesCluster, &ZigbeeClusterScenes::commandSent, thing, [=](quint8 command, const QByteArray &payload){
qCDebug(dcZigbeeTradfri()) << thing << "scene command received" << command << payload.toHex();
if (payload.count() <= 0)
return;
switch (command) {
// Note: these comands are not in the specs
case 0x07:
if (payload.at(0) == 0x00) {
qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Right";
emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Right")));
} else if (payload.at(0) == 0x01) {
qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Left";
emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Left")));
}
break;
case 0x08:
if (payload.at(0) == 0x00) {
qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Right";
emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Right")));
} else if (payload.at(0) == 0x01) {
qCDebug(dcZigbeeTradfri()) << thing << "button pressed: Left";
emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Left")));
}
break;
}
});
}
} }
if (thing->thingClassId() == soundRemoteThingClassId) { if (thing->thingClassId() == soundRemoteThingClassId) {
@ -514,109 +598,103 @@ void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNod
void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint)
{ {
// Get the current configured bindings for this node
// ZigbeeReply *reply = node->removeAllBindings(); // if (endpoint->hasOutputCluster(ZigbeeClusterLibrary::ClusterIdGroups)) {
// connect(reply, &ZigbeeReply::finished, node, [=](){ // qCDebug(dcZigbeeTradfri()) << "Try to add group...";
// if (reply->error() != ZigbeeReply::ErrorNoError) { // ZigbeeClusterGroups *groupCluster = endpoint->outputCluster<ZigbeeClusterGroups>(ZigbeeClusterLibrary::ClusterIdGroups);
// qCWarning(dcZigbeeTradfri()) << "Failed to remove all bindings for initialization of" << node; // ZigbeeClusterReply *reply = groupCluster->addGroup(0x0000, QString());
// } else { // connect(reply, &ZigbeeClusterReply::finished, node, [=](){
// qCDebug(dcZigbeeTradfri()) << "Removed all bindings successfully from" << node; // if (reply->error()) {
// } // qCWarning(dcZigbeeTradfri()) << "Failed to add remote to group 0x0000";
// return;
// }
// Bind basic cluster // });
ZigbeeDeviceObjectReply *bindRegisterReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdBasic, 0x0000); // }
connect(bindRegisterReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
if (bindRegisterReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { // Read battery, bind and configure attribute reporting for battery
qCWarning(dcZigbeeTradfri()) << "Failed to bind basic cluster" << bindRegisterReply->error(); if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) {
qCWarning(dcZigbeeTradfri()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpoint;
return;
}
qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes" << node;
ZigbeeClusterReply *readAttributeReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining});
connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error();
} else {
qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully";
}
// Bind the cluster to the coordinator
qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator IEEE address";
ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration,
hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
qCWarning(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error();
} else { } else {
qCDebug(dcZigbeeTradfri()) << "Bind basic cluster request finished successfully"; qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully";
} }
// Read battery, bind and configure attribute reporting for battery // Configure attribute reporting for battery remaining (0.5 % changes = 1)
if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) { ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
qCWarning(dcZigbeeTradfri()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpoint; reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining;
return; reportingConfig.dataType = Zigbee::Uint8;
} reportingConfig.minReportingInterval = 300;
reportingConfig.maxReportingInterval = 2700;
reportingConfig.reportableChange = ZigbeeDataType(static_cast<quint8>(1)).data();
qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes" << node; qCDebug(dcZigbeeTradfri()) << "Configure attribute reporting for power configuration cluster to coordinator";
ZigbeeClusterReply *readAttributeReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining}); ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig});
connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){ connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) { if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error(); qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error();
} else { } else {
qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully"; qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
} }
// Init OnOff cluster
// Bind the cluster to the coordinator qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator";
qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator IEEE address"; ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000);
ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration,
hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
qCWarning(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error(); qCWarning(dcZigbeeTradfri()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error();
} else { } else {
qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator finished successfully"; qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully";
} }
// Configure attribute reporting for battery remaining (0.5 % changes = 1)
ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining;
reportingConfig.dataType = Zigbee::Uint8;
reportingConfig.minReportingInterval = 300;
reportingConfig.maxReportingInterval = 2700;
reportingConfig.reportableChange = ZigbeeDataType(static_cast<quint8>(1)).data();
qCDebug(dcZigbeeTradfri()) << "Configure attribute reporting for power configuration cluster to coordinator"; // Init Level cluster
ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig}); qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator";
connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000);
if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
qCWarning(dcZigbeeTradfri()) << "Failed to bind level cluster to coordinator" << zdoReply->error();
} else { } else {
qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully";
} }
// Init OnOff cluster // Read final bindings
qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator"; qCDebug(dcZigbeeTradfri()) << "Read binding table from node" << node;
ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000); ZigbeeReply *reply = node->readBindingTableEntries();
connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ connect(reply, &ZigbeeReply::finished, node, [=](){
if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { if (reply->error() != ZigbeeReply::ErrorNoError) {
qCWarning(dcZigbeeTradfri()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error(); qCWarning(dcZigbeeTradfri()) << "Failed to read binding table from" << node;
} else { } else {
qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully"; foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) {
} qCDebug(dcZigbeeTradfri()) << node << binding;
// Init Level cluster
qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator";
ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindGroupAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000);
connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
qCWarning(dcZigbeeTradfri()) << "Failed to bind level cluster to coordinator" << zdoReply->error();
} else {
qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully";
} }
}
// Read final bindings
qCDebug(dcZigbeeTradfri()) << "Read binding table from node" << node;
ZigbeeReply *reply = node->readBindingTableEntries();
connect(reply, &ZigbeeReply::finished, node, [=](){
if (reply->error() != ZigbeeReply::ErrorNoError) {
qCWarning(dcZigbeeTradfri()) << "Failed to read binding table from" << node;
} else {
foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) {
qCDebug(dcZigbeeTradfri()) << node << binding;
}
}
});
});
}); });
}); });
}); });
}); });
}); });
//433 }); });
} }
void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint)

View File

@ -128,7 +128,7 @@
"id": "19ee3f56-f958-4b04-8ea3-747b72d7c7d6", "id": "19ee3f56-f958-4b04-8ea3-747b72d7c7d6",
"setupMethod": "JustAdd", "setupMethod": "JustAdd",
"createMethods": [ "Auto" ], "createMethods": [ "Auto" ],
"interfaces": [ "longpressmultibutton", "batterylevel", "wirelessconnectable" ], "interfaces": [ "multibutton", "batterylevel", "wirelessconnectable" ],
"paramTypes": [ "paramTypes": [
{ {
"id": "2ee18d3e-5fb8-4809-a52a-8e420b677f6e", "id": "2ee18d3e-5fb8-4809-a52a-8e420b677f6e",
@ -216,21 +216,7 @@
"name": "buttonName", "name": "buttonName",
"displayName": "Button name", "displayName": "Button name",
"type": "QString", "type": "QString",
"allowedValues": ["Power"] "allowedValues": ["Power", "Up", "Down", "Left", "Right"]
}
]
},
{
"id": "3fb1bc3b-50ea-4a9f-a5b1-10b1fae2592e",
"name": "longPressed",
"displayName": "Button longpressed",
"paramTypes": [
{
"id": "4a151723-5798-4087-a4e5-5aa08ecbb9b8",
"name": "buttonName",
"displayName": "Button name",
"type": "QString",
"allowedValues": ["Power"]
} }
] ]
} }