From 50f22c9178a16b2c539109cf34e0c0745abadc13 Mon Sep 17 00:00:00 2001 From: dvk2018 <45453148+dvk2018@users.noreply.github.com> Date: Wed, 26 Dec 2018 17:15:41 +0500 Subject: [PATCH] Multiple tags write via Server-side RPC for OPC UA extension (#151) * Implemented multiple tags write via RPC for OPC UA extension. * Fix merge conflicts. Fix license header. --- .../gateway/extensions/opc/OpcUaDevice.java | 11 +- .../extensions/opc/OpcUaDeviceAware.java | 20 ++ .../extensions/opc/OpcUaServerMonitor.java | 82 ++---- .../extensions/opc/rpc/RpcProcessor.java | 243 ++++++++++++++++++ .../extensions/opc/util/OpcUaUtils.java | 145 +++++++++++ .../thingsboard/gateway/util/JsonTools.java | 5 +- 6 files changed, 443 insertions(+), 63 deletions(-) create mode 100644 src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaDeviceAware.java create mode 100644 src/main/java/org/thingsboard/gateway/extensions/opc/rpc/RpcProcessor.java create mode 100644 src/main/java/org/thingsboard/gateway/extensions/opc/util/OpcUaUtils.java diff --git a/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaDevice.java b/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaDevice.java index d7e866b37..7c7ea564e 100644 --- a/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaDevice.java +++ b/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaDevice.java @@ -22,6 +22,7 @@ import org.thingsboard.gateway.extensions.opc.conf.mapping.DeviceMapping; import org.thingsboard.gateway.extensions.common.conf.mapping.KVMapping; import org.thingsboard.gateway.extensions.opc.conf.mapping.TimeseriesMapping; +import org.thingsboard.gateway.extensions.opc.scan.OpcUaNode; import org.thingsboard.server.common.data.kv.*; import java.util.*; @@ -33,7 +34,7 @@ @Data public class OpcUaDevice { - private final NodeId nodeId; + private final OpcUaNode opcNode; private final DeviceMapping mapping; private final Map tagKeysMap = new HashMap<>(); private final Map tagIdsMap = new HashMap<>(); @@ -68,12 +69,14 @@ private NodeId registerTag(Map.Entry kv) { return tagKeysMap.put(kv.getKey(), kv.getValue()); } - public void calculateDeviceName(Map deviceNameTagValues) { + public String calculateDeviceName(Map deviceNameTagValues) { String deviceNameTmp = mapping.getDeviceNamePattern(); for (Map.Entry kv : deviceNameTagValues.entrySet()) { deviceNameTmp = deviceNameTmp.replace(escape(kv.getKey()), kv.getValue()); } this.deviceName = deviceNameTmp; + + return this.deviceName; } public void updateTag(NodeId tagId, DataValue dataValue) { @@ -117,6 +120,10 @@ public List getAffectedTimeseries(NodeId tagId, DataValue dataValue) } } + public NodeId getTagNodeId(String tag) { + return tagKeysMap.get(tag); + } + private List getKvEntries(List mappings) { List result = new ArrayList<>(); for (KVMapping mapping : mappings) { diff --git a/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaDeviceAware.java b/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaDeviceAware.java new file mode 100644 index 000000000..42f9754e7 --- /dev/null +++ b/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaDeviceAware.java @@ -0,0 +1,20 @@ +/** + * Copyright © 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.gateway.extensions.opc; + +public interface OpcUaDeviceAware { + OpcUaDevice getDevice(String deviceName); +} diff --git a/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaServerMonitor.java b/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaServerMonitor.java index f42749f49..6acc2913e 100644 --- a/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaServerMonitor.java +++ b/src/main/java/org/thingsboard/gateway/extensions/opc/OpcUaServerMonitor.java @@ -27,16 +27,16 @@ import org.eclipse.milo.opcua.stack.core.AttributeId; import org.eclipse.milo.opcua.stack.core.Identifiers; import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; -import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; -import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; -import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; -import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName; -import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger; +import org.eclipse.milo.opcua.stack.core.types.builtin.*; +import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.*; import org.eclipse.milo.opcua.stack.core.types.enumerated.*; import org.eclipse.milo.opcua.stack.core.types.structured.*; import org.thingsboard.gateway.extensions.opc.conf.OpcUaServerConfiguration; import org.thingsboard.gateway.extensions.opc.conf.mapping.DeviceMapping; +import org.thingsboard.gateway.extensions.opc.rpc.RpcProcessor; import org.thingsboard.gateway.extensions.opc.scan.OpcUaNode; +import org.thingsboard.gateway.extensions.opc.util.OpcUaUtils; +import org.thingsboard.gateway.service.data.RpcCommandSubscription; import org.thingsboard.gateway.service.gateway.GatewayService; import org.thingsboard.gateway.util.CertificateInfo; import org.thingsboard.gateway.util.ConfigurationTools; @@ -60,7 +60,7 @@ * Created by ashvayka on 16.01.17. */ @Slf4j -public class OpcUaServerMonitor { +public class OpcUaServerMonitor implements OpcUaDeviceAware { private final GatewayService gateway; private final OpcUaServerConfiguration configuration; @@ -69,17 +69,21 @@ public class OpcUaServerMonitor { private UaSubscription subscription; private Map devices; private Map> devicesByTags; + private Map devicesByName; private Map mappings; private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private final AtomicLong clientHandles = new AtomicLong(1L); + private RpcProcessor rpcProcessor; + public OpcUaServerMonitor(GatewayService gateway, OpcUaServerConfiguration configuration) { this.gateway = gateway; this.configuration = configuration; this.devices = new HashMap<>(); this.devicesByTags = new HashMap<>(); this.mappings = configuration.getMapping().stream().collect(Collectors.toMap(m -> Pattern.compile(m.getDeviceNodePattern()), Function.identity())); + this.devicesByName = new HashMap<>(); } public void connect(Boolean isRemote) { @@ -101,6 +105,7 @@ public void connect(Boolean isRemote) { client.connect().get(); subscription = client.getSubscriptionManager().createSubscription(1000.0).get(); + rpcProcessor = new RpcProcessor(gateway, client, this); scanForDevices(); } catch (Exception e) { @@ -161,6 +166,11 @@ public void scanForDevices() { }, configuration.getScanPeriodInSeconds(), TimeUnit.SECONDS); } + @Override + public OpcUaDevice getDevice(String deviceName) { + return devicesByName.get(deviceName); + } + private void scanForDevices(OpcUaNode node) { log.trace("Scanning node: {}", node); List matchedMappings = mappings.entrySet().stream() @@ -176,7 +186,7 @@ private void scanForDevices(OpcUaNode node) { }); try { - BrowseResult browseResult = client.browse(getBrowseDescription(node.getNodeId())).get(); + BrowseResult browseResult = client.browse(OpcUaUtils.getBrowseDescription(node.getNodeId())).get(); List references = toList(browseResult.getReferences()); for (ReferenceDescription rd : references) { @@ -201,14 +211,14 @@ private void scanDevice(OpcUaNode node, DeviceMapping m) throws Exception { log.debug("Scanning device node: {}", node); Set tags = m.getAllTags(); log.debug("Scanning node hierarchy for tags: {}", tags); - Map tagMap = lookupTags(node.getNodeId(), node.getName(), tags); + Map tagMap = OpcUaUtils.lookupTags(client, node.getNodeId(), node.getName(), tags); log.debug("Scanned {} tags out of {}", tagMap.size(), tags.size()); OpcUaDevice device; if (devices.containsKey(node.getNodeId())) { device = devices.get(node.getNodeId()); } else { - device = new OpcUaDevice(node.getNodeId(), m); + device = new OpcUaDevice(node, m); devices.put(node.getNodeId(), device); Map deviceNameTags = new HashMap<>(); @@ -221,8 +231,12 @@ private void scanDevice(OpcUaNode node, DeviceMapping m) throws Exception { deviceNameTags.put(tag, tagNode); } } - device.calculateDeviceName(readTags(deviceNameTags)); - gateway.onDeviceConnect(device.getDeviceName(), null); + + String deviceName = device.calculateDeviceName(readTags(deviceNameTags)); + devicesByName.put(deviceName, device); + + gateway.onDeviceConnect(deviceName, null); + gateway.subscribe(new RpcCommandSubscription(deviceName, rpcProcessor)); } device.updateScanTs(); @@ -313,50 +327,4 @@ private Map readTags(Map tags) throws ExecutionE } return result; } - - private Map lookupTags(NodeId nodeId, String deviceNodeName, Set tags) { - Map values = new HashMap<>(); - try { - BrowseResult browseResult = client.browse(getBrowseDescription(nodeId)).get(); - List references = toList(browseResult.getReferences()); - - for (ReferenceDescription rd : references) { - NodeId childId; - if (rd.getNodeId().isLocal()) { - childId = rd.getNodeId().local().get(); - } else { - log.trace("Ignoring remote node: {}", rd.getNodeId()); - continue; - } - - String browseName = rd.getBrowseName().getName(); - String name; - String childIdStr = childId.getIdentifier().toString(); - if (childIdStr.contains(deviceNodeName)) { - name = childIdStr.substring(childIdStr.indexOf(deviceNodeName) + deviceNodeName.length() + 1, childIdStr.length()); - } else { - name = rd.getBrowseName().getName(); - } - if (tags.contains(name)) { - values.put(name, childId); - } - // recursively browse to children - values.putAll(lookupTags(childId, deviceNodeName, tags)); - } - } catch (InterruptedException | ExecutionException e) { - log.error("Browsing nodeId={} failed: {}", nodeId, e.getMessage(), e); - } - return values; - } - - private BrowseDescription getBrowseDescription(NodeId nodeId) { - return new BrowseDescription( - nodeId, - BrowseDirection.Forward, - Identifiers.References, - true, - uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()), - uint(BrowseResultMask.All.getValue()) - ); - } } diff --git a/src/main/java/org/thingsboard/gateway/extensions/opc/rpc/RpcProcessor.java b/src/main/java/org/thingsboard/gateway/extensions/opc/rpc/RpcProcessor.java new file mode 100644 index 000000000..9ba25d476 --- /dev/null +++ b/src/main/java/org/thingsboard/gateway/extensions/opc/rpc/RpcProcessor.java @@ -0,0 +1,243 @@ +/** + * Copyright © 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.gateway.extensions.opc.rpc; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.collect.Sets; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.milo.opcua.sdk.client.OpcUaClient; +import org.eclipse.milo.opcua.stack.core.AttributeId; +import org.eclipse.milo.opcua.stack.core.types.builtin.*; +import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; +import org.eclipse.milo.opcua.stack.core.types.structured.ReadResponse; +import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId; +import org.eclipse.milo.opcua.stack.core.types.structured.WriteResponse; +import org.eclipse.milo.opcua.stack.core.types.structured.WriteValue; +import org.thingsboard.gateway.extensions.opc.OpcUaDevice; +import org.thingsboard.gateway.extensions.opc.OpcUaDeviceAware; +import org.thingsboard.gateway.extensions.opc.scan.OpcUaNode; +import org.thingsboard.gateway.extensions.opc.util.OpcUaUtils; +import org.thingsboard.gateway.service.RpcCommandListener; +import org.thingsboard.gateway.service.data.RpcCommandData; +import org.thingsboard.gateway.service.data.RpcCommandResponse; +import org.thingsboard.gateway.service.gateway.GatewayService; +import org.thingsboard.gateway.util.JsonTools; + +import java.util.*; + +@Slf4j +public class RpcProcessor implements RpcCommandListener { + private static final String RESPONSE_STATUS_OK = "ok"; + private static final String RESPONSE_FIELD_ERROR = "error"; + + private static final String RPC_WRITE = "write"; + + private final GatewayService gateway; + private final OpcUaClient client; + private final OpcUaDeviceAware deviceContainer; + + public RpcProcessor(GatewayService gateway, OpcUaClient client, OpcUaDeviceAware deviceContainer) { + this.gateway = gateway; + this.client = client; + this.deviceContainer = deviceContainer; + } + + @Override + public void onRpcCommand(String deviceName, RpcCommandData command) { + log.debug("RPC received: device='{}', command={}", deviceName, command); + + OpcUaDevice device = deviceContainer.getDevice(deviceName); + if (device == null) { + log.warn("No device '{}' found for RPC {}", deviceName, command); + gateway.onDeviceRpcResponse(createErrorResponse(command.getRequestId(), + deviceName, + String.format("No device '%s' found", deviceName))); + return; + } + + if (!RPC_WRITE.equals(command.getMethod())) { + log.warn("Unknown RPC method '{}'", command.getMethod()); + gateway.onDeviceRpcResponse(createErrorResponse(command.getRequestId(), + deviceName, + String.format("Unsupported RPC method '%s'", command.getMethod()))); + return; + } + + Map results = new HashMap<>(); + HashMap values = JsonTools.fromString(command.getParams(), new TypeReference>() {}); + + Map nodeIds = resolveNodeIds(device, values.keySet()); + Set notFoundTags = Sets.difference(values.keySet(), nodeIds.keySet()); + + if (!notFoundTags.isEmpty()) { + log.warn("Failed to find tags {}", notFoundTags); + results.putAll(createTagsErrorResponse(notFoundTags, "No tag found")); + } + + Map types = requestOpcTypes(nodeIds); + Set notResolvedTags = Sets.difference(nodeIds.keySet(), types.keySet()); + + if (!notResolvedTags.isEmpty()) { + log.warn("Failed to resolve OPC type for tags {}", notResolvedTags); + results.putAll(createTagsErrorResponse(notResolvedTags, "Failed to resolve OPC type")); + } + + try { + List request = createWriteRequest(nodeIds, types, values, results); + + log.trace("Writing values to OPC server for tags {}", types.keySet()); + + WriteResponse writeResponse = client.write(request).get(); + results.putAll(processWriteResponse(writeResponse, types)); + } catch (Exception e) { + log.warn("OPC write failed", e); + results.putAll(createTagsErrorResponse(types.keySet(), "OPC write failed: " + e.getMessage())); + } + + gateway.onDeviceRpcResponse(createResponse(command.getRequestId(), deviceName, results)); + } + + private List createWriteRequest(Map tags, + Map types, + Map values, + Map results) { + List opcValues = new LinkedList<>(); + Set failedTags= new HashSet<>(); + + types.forEach((tag, typeNodeId) -> { + Object rawValue = values.get(tag); + try { + Variant opcValue = OpcUaUtils.convertToOpcValue(typeNodeId, rawValue); + opcValues.add(new WriteValue(tags.get(tag), AttributeId.Value.uid(), null, DataValue.valueOnly(opcValue))); + } catch (Exception e) { + log.warn("Failed to convert '{}' tag's value '{}' to OPC format (OPC type node {})", tag, rawValue, typeNodeId, e); + results.put(tag, e.getMessage()); + failedTags.add(tag); + } + }); + + if (!failedTags.isEmpty()) { + types.keySet().removeAll(failedTags); + } + + return opcValues; + } + + private Map processWriteResponse(WriteResponse response, Map tags) { + Map results = new HashMap<>(); + + int i = 0; + for (String tag : tags.keySet()) { + StatusCode statusCode = response.getResults()[i++]; + if (statusCode.isGood()) { + log.debug("OPC write success for tag '{}'", tag); + results.put(tag, RESPONSE_STATUS_OK); + } else { + log.warn("OPC write failed for tag '{}': reason={}", tag, statusCode); + results.put(tag, "OPC error code: " + statusCode); + } + } + + return results; + } + + private Map requestOpcTypes(Map tags) { + Map resolvedTypes = new HashMap<>(); + + try { + log.trace("Requesting OPC data type for tags {}", tags.keySet()); + + List readIds = new LinkedList<>(); + tags.forEach((tag, nodeId) -> readIds.add( + new ReadValueId(nodeId, AttributeId.DataType.uid(), null, QualifiedName.NULL_VALUE))); + + ReadResponse readResponse = client.read(0.0, TimestampsToReturn.Neither, readIds).get(); + + int readRespIndex = 0; + for (Map.Entry entry : tags.entrySet()) { + DataValue dv = readResponse.getResults()[readRespIndex++]; + + if (dv.getStatusCode().isGood()) { + NodeId type = (NodeId) dv.getValue().getValue(); + log.trace("Got OPC type node for tag '{}': {}", entry.getKey(), type); + + resolvedTypes.put(entry.getKey(), type); + } else { + log.warn("Failed to request OPC type node for tag '{}': reason={}", entry.getKey(), dv.getStatusCode()); + } + } + } catch (Exception e) { + log.warn("Failed to request OPC data type for tags '{}': ", tags.keySet(), e); + } + + return resolvedTypes; + } + + private Map resolveNodeIds(OpcUaDevice device, Set tags) { + Set pendingTags = new HashSet<>(); + Map tagsToNodeId = new HashMap<>(); + + for (String tag : tags) { + NodeId tagNodeId = device.getTagNodeId(tag); + if (tagNodeId == null) { + pendingTags.add(tag); + } else { + tagsToNodeId.put(tag, tagNodeId); + } + } + + if (!pendingTags.isEmpty()) { + log.trace("Looking up OPC server for tags {}", pendingTags); + + OpcUaNode node = device.getOpcNode(); + Map resolvedTags = OpcUaUtils.lookupTags(client, + node.getNodeId(), + node.getName(), + pendingTags); + tagsToNodeId.putAll(resolvedTags); + } + + return tagsToNodeId; + } + + private Map createTagsErrorResponse(Set tags, String error) { + Map errors = new HashMap<>(); + tags.forEach((tag)->errors.put(tag, error)); + return errors; + } + + private RpcCommandResponse createErrorResponse(int requestId, String deviceName, String errorDescription) { + RpcCommandResponse response = new RpcCommandResponse(); + response.setRequestId(requestId); + response.setDeviceName(deviceName); + + Map data = new HashMap<>(); + data.put(RESPONSE_FIELD_ERROR, errorDescription); + + response.setData(JsonTools.toString(data)); + + return response; + } + + private RpcCommandResponse createResponse(int requestId, String deviceName, Map data) { + RpcCommandResponse response = new RpcCommandResponse(); + response.setRequestId(requestId); + response.setDeviceName(deviceName); + response.setData(JsonTools.toString(data)); + + return response; + } +} diff --git a/src/main/java/org/thingsboard/gateway/extensions/opc/util/OpcUaUtils.java b/src/main/java/org/thingsboard/gateway/extensions/opc/util/OpcUaUtils.java new file mode 100644 index 000000000..a2a02b0b8 --- /dev/null +++ b/src/main/java/org/thingsboard/gateway/extensions/opc/util/OpcUaUtils.java @@ -0,0 +1,145 @@ +/** + * Copyright © 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.gateway.extensions.opc.util; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.milo.opcua.sdk.client.OpcUaClient; +import org.eclipse.milo.opcua.stack.core.Identifiers; +import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString; +import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; +import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; +import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; +import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UByte; +import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger; +import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.ULong; +import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort; +import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseDirection; +import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseResultMask; +import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass; +import org.eclipse.milo.opcua.stack.core.types.structured.BrowseDescription; +import org.eclipse.milo.opcua.stack.core.types.structured.BrowseResult; +import org.eclipse.milo.opcua.stack.core.types.structured.ReferenceDescription; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ExecutionException; + +import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint; +import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.toList; + +@Slf4j +public class OpcUaUtils { + public static final String DATE_TIME_FORMAT = "yyyy-M-d H:m:s.SSS z"; + + public static Map lookupTags(OpcUaClient client, NodeId nodeId, String deviceNodeName, Set tags) { + Map values = new HashMap<>(); + try { + BrowseResult browseResult = client.browse(getBrowseDescription(nodeId)).get(); + List references = toList(browseResult.getReferences()); + + for (ReferenceDescription rd : references) { + NodeId childId; + if (rd.getNodeId().isLocal()) { + childId = rd.getNodeId().local().get(); + } else { + log.trace("Ignoring remote node: {}", rd.getNodeId()); + continue; + } + + String browseName = rd.getBrowseName().getName(); + String name; + String childIdStr = childId.getIdentifier().toString(); + if (childIdStr.contains(deviceNodeName)) { + name = childIdStr.substring(childIdStr.indexOf(deviceNodeName) + deviceNodeName.length() + 1, childIdStr.length()); + } else { + name = rd.getBrowseName().getName(); + } + if (tags.contains(name)) { + values.put(name, childId); + } + // recursively browse to children + values.putAll(lookupTags(client, childId, deviceNodeName, tags)); + } + } catch (InterruptedException | ExecutionException e) { + log.error("Browsing nodeId={} failed: {}", nodeId, e.getMessage(), e); + } + return values; + } + + public static BrowseDescription getBrowseDescription(NodeId nodeId) { + return new BrowseDescription( + nodeId, + BrowseDirection.Forward, + Identifiers.References, + true, + uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()), + uint(BrowseResultMask.All.getValue()) + ); + } + + public static Variant convertToOpcValue(NodeId typeNode, Object value) { + Number numberValue = (value instanceof Number) ? (Number) value : null; + + try { + if (typeNode.equals(Identifiers.Double)) { + return new Variant(Double.valueOf(numberValue.doubleValue())); + } else if (typeNode.equals(Identifiers.Float)) { + return new Variant(Float.valueOf(numberValue.floatValue())); + } else if (typeNode.equals(Identifiers.String)) { + return new Variant(value); + } else if (typeNode.equals(Identifiers.Integer)|| typeNode.equals(Identifiers.Int32)) { + return new Variant(Integer.valueOf(numberValue.intValue())); + } else if (typeNode.equals(Identifiers.Int16)) { + return new Variant(Short.valueOf(numberValue.shortValue())); + } else if (typeNode.equals(Identifiers.Int64)) { + return new Variant(Long.valueOf(numberValue.longValue())); + } else if (typeNode.equals(Identifiers.UInteger) || typeNode.equals(Identifiers.UInt32)) { + return new Variant(UInteger.valueOf(numberValue.intValue())); + } else if (typeNode.equals(Identifiers.UInt16)) { + return new Variant(UShort.valueOf(numberValue.shortValue())); + } else if (typeNode.equals(Identifiers.UInt64)) { + return new Variant(ULong.valueOf(numberValue.longValue())); + } else if (typeNode.equals(Identifiers.Boolean)) { + return new Variant(value); + } else if (typeNode.equals(Identifiers.Byte)) { + return new Variant(UByte.valueOf(numberValue.byteValue())); + } else if (typeNode.equals(Identifiers.SByte)) { + return new Variant(Byte.valueOf(numberValue.byteValue())); + } else if (typeNode.equals(Identifiers.ByteString)) { + return new Variant(ByteString.of(Base64.getDecoder().decode((String) value))); + } else if (typeNode.equals(Identifiers.DateTime)) { + if (value instanceof String) { + DateFormat df = new SimpleDateFormat(DATE_TIME_FORMAT); + return new Variant(new DateTime(df.parse((String) value))); + } else { + return new Variant(new DateTime(new Date(numberValue.longValue()))); + } + } else { + Integer opcType = ((Integer) typeNode.getIdentifier()); + log.error("Failed to convert value '{}' to OPC format: unsupported OPC type {}", value, opcType); + throw new IllegalArgumentException("Unsupported OPC type " + opcType); + } + } catch (ParseException e) { + log.error("Failed to convert value '{}' to OPC format: wrong date/time format", value); + throw new IllegalArgumentException(String.format("Wrong date/time format. Expected '%s'", DATE_TIME_FORMAT)); + } catch (Exception e) { + log.error("Failed to cast value to opc type", e); + throw e; + } + } +} diff --git a/src/main/java/org/thingsboard/gateway/util/JsonTools.java b/src/main/java/org/thingsboard/gateway/util/JsonTools.java index 11129f71b..72b6e5347 100644 --- a/src/main/java/org/thingsboard/gateway/util/JsonTools.java +++ b/src/main/java/org/thingsboard/gateway/util/JsonTools.java @@ -24,10 +24,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Created by ashvayka on 19.01.17.