Implementation of RPC call action

This commit is contained in:
Andrew Shvayka 2017-09-18 02:26:12 +03:00
parent 316178e41a
commit 257ca5db5f
12 changed files with 194 additions and 27 deletions

View File

@ -44,6 +44,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.plugin.PluginService; import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleService; import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -109,6 +110,9 @@ public class ActorSystemContext {
@Autowired @Autowired
@Getter private AlarmService alarmService; @Getter private AlarmService alarmService;
@Autowired
@Getter private RelationService relationService;
@Autowired @Autowired
@Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint; @Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint;

View File

@ -33,6 +33,8 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery; import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleMetaData; import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
@ -394,6 +396,16 @@ public final class PluginProcessingContext implements PluginContext {
} }
} }
@Override
public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) {
return this.pluginCtx.relationService.findByFromAndType(from, relationType, RelationTypeGroup.COMMON);
}
@Override
public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId from, String relationType) {
return this.pluginCtx.relationService.findByToAndType(from, relationType, RelationTypeGroup.COMMON);
}
@Override @Override
public Optional<ServerAddress> resolve(EntityId entityId) { public Optional<ServerAddress> resolve(EntityId entityId) {
return pluginCtx.routingService.resolveById(entityId); return pluginCtx.routingService.resolveById(entityId);

View File

@ -30,6 +30,7 @@ import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.plugin.PluginService; import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleService; import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -61,6 +62,7 @@ public final class SharedPluginProcessingContext {
final AttributesService attributesService; final AttributesService attributesService;
final ClusterRpcService rpcService; final ClusterRpcService rpcService;
final ClusterRoutingService routingService; final ClusterRoutingService routingService;
final RelationService relationService;
final PluginId pluginId; final PluginId pluginId;
final TenantId tenantId; final TenantId tenantId;
@ -83,6 +85,7 @@ public final class SharedPluginProcessingContext {
this.pluginService = sysContext.getPluginService(); this.pluginService = sysContext.getPluginService();
this.customerService = sysContext.getCustomerService(); this.customerService = sysContext.getCustomerService();
this.tenantService = sysContext.getTenantService(); this.tenantService = sysContext.getTenantService();
this.relationService = sysContext.getRelationService();
} }
public PluginId getPluginId() { public PluginId getPluginId() {

View File

@ -15,11 +15,14 @@
*/ */
package org.thingsboard.server.extensions.api.plugins; package org.thingsboard.server.extensions.api.plugins;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery; import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
@ -109,4 +112,12 @@ public interface PluginContext {
void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback); void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback);
/*
* Relations API
* */
ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType);
ListenableFuture<List<EntityRelation>> findByToAndType(EntityId from, String relationType);
} }

View File

@ -1,3 +1,18 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.action.rpc; package org.thingsboard.server.extensions.core.action.rpc;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -28,7 +43,8 @@ public class ServerSideRpcCallAction extends SimpleRuleLifecycleComponent implem
private ServerSideRpcCallActionConfiguration configuration; private ServerSideRpcCallActionConfiguration configuration;
private Optional<Template> deviceIdTemplate; private Optional<Template> deviceIdTemplate;
private Optional<Template> deviceRelationTemplate; private Optional<Template> fromDeviceRelationTemplate;
private Optional<Template> toDeviceRelationTemplate;
private Optional<Template> rpcCallMethodTemplate; private Optional<Template> rpcCallMethodTemplate;
private Optional<Template> rpcCallBodyTemplate; private Optional<Template> rpcCallBodyTemplate;
@ -37,7 +53,8 @@ public class ServerSideRpcCallAction extends SimpleRuleLifecycleComponent implem
this.configuration = configuration; this.configuration = configuration;
try { try {
deviceIdTemplate = toTemplate(configuration.getDeviceIdTemplate(), "Device Id Template"); deviceIdTemplate = toTemplate(configuration.getDeviceIdTemplate(), "Device Id Template");
deviceRelationTemplate = toTemplate(configuration.getDeviceRelationTemplate(), "Device Relation Template"); fromDeviceRelationTemplate = toTemplate(configuration.getFromDeviceRelationTemplate(), "From Device Relation Template");
toDeviceRelationTemplate = toTemplate(configuration.getToDeviceRelationTemplate(), "To Device Relation Template");
rpcCallMethodTemplate = toTemplate(configuration.getRpcCallMethodTemplate(), "RPC Call Method Template"); rpcCallMethodTemplate = toTemplate(configuration.getRpcCallMethodTemplate(), "RPC Call Method Template");
rpcCallBodyTemplate = toTemplate(configuration.getRpcCallBodyTemplate(), "RPC Call Body Template"); rpcCallBodyTemplate = toTemplate(configuration.getRpcCallBodyTemplate(), "RPC Call Body Template");
} catch (ParseException e) { } catch (ParseException e) {
@ -55,7 +72,8 @@ public class ServerSideRpcCallAction extends SimpleRuleLifecycleComponent implem
ServerSideRpcCallActionMsg.ServerSideRpcCallActionMsgBuilder builder = ServerSideRpcCallActionMsg.builder(); ServerSideRpcCallActionMsg.ServerSideRpcCallActionMsgBuilder builder = ServerSideRpcCallActionMsg.builder();
deviceIdTemplate.ifPresent(t -> builder.deviceId(VelocityUtils.merge(t, context))); deviceIdTemplate.ifPresent(t -> builder.deviceId(VelocityUtils.merge(t, context)));
deviceRelationTemplate.ifPresent(t -> builder.deviceRelation(VelocityUtils.merge(t, context))); fromDeviceRelationTemplate.ifPresent(t -> builder.fromDeviceRelation(VelocityUtils.merge(t, context)));
toDeviceRelationTemplate.ifPresent(t -> builder.toDeviceRelation(VelocityUtils.merge(t, context)));
rpcCallMethodTemplate.ifPresent(t -> builder.rpcCallMethod(VelocityUtils.merge(t, context))); rpcCallMethodTemplate.ifPresent(t -> builder.rpcCallMethod(VelocityUtils.merge(t, context)));
rpcCallBodyTemplate.ifPresent(t -> builder.rpcCallBody(VelocityUtils.merge(t, context))); rpcCallBodyTemplate.ifPresent(t -> builder.rpcCallBody(VelocityUtils.merge(t, context)));
return Optional.of(new ServerSideRpcCallRuleToPluginActionMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(), toDeviceActorMsg.getDeviceId(), return Optional.of(new ServerSideRpcCallRuleToPluginActionMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(), toDeviceActorMsg.getDeviceId(),

View File

@ -26,7 +26,9 @@ public class ServerSideRpcCallActionConfiguration {
private String sendFlag; private String sendFlag;
private String deviceIdTemplate; private String deviceIdTemplate;
private String deviceRelationTemplate;
private String rpcCallMethodTemplate; private String rpcCallMethodTemplate;
private String rpcCallBodyTemplate; private String rpcCallBodyTemplate;
private long rpcCallTimeoutInSec;
private String fromDeviceRelationTemplate;
private String toDeviceRelationTemplate;
} }

View File

@ -1,3 +1,18 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.action.rpc; package org.thingsboard.server.extensions.core.action.rpc;
import lombok.Builder; import lombok.Builder;
@ -13,8 +28,11 @@ import java.io.Serializable;
public class ServerSideRpcCallActionMsg implements Serializable { public class ServerSideRpcCallActionMsg implements Serializable {
private String deviceId; private String deviceId;
private String deviceRelation;
private String rpcCallMethod; private String rpcCallMethod;
private String rpcCallBody; private String rpcCallBody;
private long rpcCallTimeoutInSec;
private String fromDeviceRelation;
private String toDeviceRelation;
} }

View File

@ -1,3 +1,18 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.action.rpc; package org.thingsboard.server.extensions.core.action.rpc;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;

View File

@ -1,12 +1,12 @@
/** /**
* Copyright © 2016-2017 The Thingsboard Authors * Copyright © 2016-2017 The Thingsboard Authors
* <p> *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* <p> *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* <p> *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -26,6 +26,7 @@ import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallAction; import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallAction;
import org.thingsboard.server.extensions.core.plugin.rpc.handlers.RpcRestMsgHandler; import org.thingsboard.server.extensions.core.plugin.rpc.handlers.RpcRestMsgHandler;
import org.thingsboard.server.extensions.core.plugin.rpc.handlers.RpcRuleMsgHandler;
/** /**
* @author Andrew Shvayka * @author Andrew Shvayka
@ -65,7 +66,7 @@ public class RpcPlugin extends AbstractPlugin<RpcPluginConfiguration> {
@Override @Override
protected RuleMsgHandler getRuleMsgHandler() { protected RuleMsgHandler getRuleMsgHandler() {
return new DefaultRuleMsgHandler(); return new RpcRuleMsgHandler();
} }
@Override @Override

View File

@ -1,31 +1,102 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.plugin.rpc.handlers; package org.thingsboard.server.extensions.core.plugin.rpc.handlers;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleId; import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import org.thingsboard.server.extensions.api.plugins.PluginContext; import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody;
import org.thingsboard.server.extensions.api.rules.RuleException; import org.thingsboard.server.extensions.api.rules.RuleException;
import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallActionMsg; import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallActionMsg;
import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallRuleToPluginActionMsg;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** /**
* Created by ashvayka on 14.09.17. * Created by ashvayka on 14.09.17.
*/ */
@Slf4j
public class RpcRuleMsgHandler implements RuleMsgHandler { public class RpcRuleMsgHandler implements RuleMsgHandler {
@Override @Override
public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException { public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException {
if (msg instanceof ServerSideRpcCallActionMsg) { if (msg instanceof ServerSideRpcCallRuleToPluginActionMsg) {
handle(ctx, tenantId, ruleId, (ServerSideRpcCallActionMsg) msg); handle(ctx, tenantId, ruleId, ((ServerSideRpcCallRuleToPluginActionMsg) msg).getPayload());
} else { } else {
throw new RuntimeException("Not supported msg: " + msg + "!"); throw new RuntimeException("Not supported msg: " + msg + "!");
} }
} }
private void handle(PluginContext ctx, TenantId tenantId, RuleId ruleId, ServerSideRpcCallActionMsg msg) { private void handle(final PluginContext ctx, TenantId tenantId, RuleId ruleId, ServerSideRpcCallActionMsg msg) {
// TODO: implement DeviceId deviceId = new DeviceId(UUID.fromString(msg.getDeviceId()));
// ToDeviceRpcRequest request = new ToDeviceRpcRequest(); ctx.checkAccess(deviceId, new PluginCallback<Void>() {
// ctx.sendRpcRequest(request); @Override
public void onSuccess(PluginContext dummy, Void value) {
try {
List<EntityId> deviceIds;
if (StringUtils.isEmpty(msg.getFromDeviceRelation()) && StringUtils.isEmpty(msg.getToDeviceRelation())) {
deviceIds = Collections.singletonList(deviceId);
} else if (!StringUtils.isEmpty(msg.getFromDeviceRelation())) {
List<EntityRelation> relations = ctx.findByFromAndType(deviceId, msg.getFromDeviceRelation()).get();
deviceIds = relations.stream().map(EntityRelation::getTo).collect(Collectors.toList());
} else {
List<EntityRelation> relations = ctx.findByToAndType(deviceId, msg.getFromDeviceRelation()).get();
deviceIds = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toList());
}
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(msg.getRpcCallMethod(), msg.getRpcCallBody());
long expirationTime = System.currentTimeMillis() + msg.getRpcCallTimeoutInSec();
for (EntityId address : deviceIds) {
DeviceId tmpId = new DeviceId(address.getId());
ctx.checkAccess(tmpId, new PluginCallback<Void>() {
@Override
public void onSuccess(PluginContext ctx, Void value) {
ctx.sendRpcRequest(new ToDeviceRpcRequest(UUID.randomUUID(),
tenantId, tmpId, true, expirationTime, body)
);
log.trace("[{}] Sent RPC Call Action msg", tmpId);
}
@Override
public void onFailure(PluginContext ctx, Exception e) {
log.info("[{}] Failed to process RPC Call Action msg", tmpId, e);
}
});
}
} catch (Exception e) {
log.info("Failed to process RPC Call Action msg", e);
}
}
@Override
public void onFailure(PluginContext dummy, Exception e) {
log.info("[{}] Failed to process RPC Call Action msg", deviceId, e);
}
});
} }
} }

View File

@ -4,7 +4,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"sendFlag": { "sendFlag": {
"title": "Send flag", "title": "Send flag (empty or 'isNewAlarm', 'isExistingAlarm', 'isClearedAlarm', 'isNewOrClearedAlarm')",
"type": "string" "type": "string"
}, },
"fromTemplate": { "fromTemplate": {

View File

@ -4,16 +4,13 @@
"type": "object", "type": "object",
"properties": { "properties": {
"sendFlag": { "sendFlag": {
"title": "Send flag", "title": "Send flag (empty or 'isNewAlarm', 'isExistingAlarm', 'isClearedAlarm', 'isNewOrClearedAlarm')",
"type": "string" "type": "string"
}, },
"deviceIdTemplate": { "deviceIdTemplate": {
"title": "Device ID template", "title": "Device ID template",
"type": "string" "type": "string",
}, "default": "$deviceId"
"deviceRelationTemplate": {
"title": "Device Relation template",
"type": "string"
}, },
"rpcCallMethodTemplate": { "rpcCallMethodTemplate": {
"title": "RPC Call template", "title": "RPC Call template",
@ -22,24 +19,39 @@
"rpcCallBodyTemplate": { "rpcCallBodyTemplate": {
"title": "RPC Call Body template", "title": "RPC Call Body template",
"type": "string" "type": "string"
},
"rpcCallTimeoutInSec": {
"title": "RPC Call timeout in seconds",
"type": "integer",
"default": 60
},
"fromDeviceRelationTemplate": {
"title": "From Device Relation template",
"type": "string"
},
"toDeviceRelationTemplate": {
"title": "To Device Relation template",
"type": "string"
} }
}, },
"required": [ "required": [
"deviceIdTemplate", "deviceIdTemplate",
"deviceRelationTemplate",
"rpcCallMethodTemplate", "rpcCallMethodTemplate",
"rpcCallBodyTemplate" "rpcCallBodyTemplate",
"rpcCallTimeoutInSec"
] ]
}, },
"form": [ "form": [
"sendFlag", "sendFlag",
"deviceIdTemplate", "deviceIdTemplate",
"deviceRelationTemplate",
"rpcCallMethodTemplate", "rpcCallMethodTemplate",
{ {
"key": "rpcCallBodyTemplate", "key": "rpcCallBodyTemplate",
"type": "textarea", "type": "textarea",
"rows": 5 "rows": 5
} },
"rpcCallTimeoutInSec",
"fromDeviceRelationTemplate",
"toDeviceRelationTemplate"
] ]
} }