package com.serotonin.bacnet4j.util; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import com.serotonin.bacnet4j.LocalDevice; import com.serotonin.bacnet4j.RemoteDevice; import com.serotonin.bacnet4j.RemoteObject; import com.serotonin.bacnet4j.exception.AbortAPDUException; import com.serotonin.bacnet4j.exception.BACnetException; import com.serotonin.bacnet4j.exception.BACnetTimeoutException; import com.serotonin.bacnet4j.exception.ErrorAPDUException; import com.serotonin.bacnet4j.obj.ObjectProperties; import com.serotonin.bacnet4j.service.acknowledgement.ReadPropertyAck; import com.serotonin.bacnet4j.service.acknowledgement.ReadPropertyMultipleAck; import com.serotonin.bacnet4j.service.confirmed.ReadPropertyMultipleRequest; import com.serotonin.bacnet4j.service.confirmed.ReadPropertyRequest; import com.serotonin.bacnet4j.service.confirmed.WritePropertyRequest; import com.serotonin.bacnet4j.type.Encodable; import com.serotonin.bacnet4j.type.constructed.BACnetError; import com.serotonin.bacnet4j.type.constructed.ObjectPropertyReference; import com.serotonin.bacnet4j.type.constructed.PropertyReference; import com.serotonin.bacnet4j.type.constructed.ReadAccessResult; import com.serotonin.bacnet4j.type.constructed.ReadAccessResult.Result; import com.serotonin.bacnet4j.type.constructed.ReadAccessSpecification; import com.serotonin.bacnet4j.type.constructed.SequenceOf; import com.serotonin.bacnet4j.type.constructed.ServicesSupported; import com.serotonin.bacnet4j.type.enumerated.AbortReason; import com.serotonin.bacnet4j.type.enumerated.ErrorClass; import com.serotonin.bacnet4j.type.enumerated.ErrorCode; import com.serotonin.bacnet4j.type.enumerated.PropertyIdentifier; import com.serotonin.bacnet4j.type.primitive.ObjectIdentifier; import com.serotonin.bacnet4j.type.primitive.UnsignedInteger; public class RequestUtils { private static final Logger LOG = Logger.getLogger(RequestUtils.class.toString()); /** * Does not work with aggregate PIDs like "all". */ public static Encodable getProperty(LocalDevice localDevice, RemoteDevice d, PropertyIdentifier pid) throws BACnetException { return getProperty(localDevice, d, d.getObjectIdentifier(), pid); } /** * Does not work with aggregate PIDs like "all". */ public static Encodable getProperty(LocalDevice localDevice, RemoteDevice d, ObjectIdentifier oid, PropertyIdentifier pid) throws BACnetException { Map<PropertyIdentifier, Encodable> map = getProperties(localDevice, d, oid, null, pid); return map.get(pid); } public static Map<PropertyIdentifier, Encodable> getProperties(LocalDevice localDevice, RemoteDevice d, RequestListener callback, PropertyIdentifier... pids) throws BACnetException { return getProperties(localDevice, d, d.getObjectIdentifier(), callback, pids); } public static Map<PropertyIdentifier, Encodable> getProperties(LocalDevice localDevice, RemoteDevice d, ObjectIdentifier obj, RequestListener callback, PropertyIdentifier... pids) throws BACnetException { List<ObjectPropertyReference> refs = new ArrayList<ObjectPropertyReference>(pids.length); for (int i = 0; i < pids.length; i++) refs.add(new ObjectPropertyReference(obj, pids[i])); return getProperties(localDevice, d, callback, refs); } private static Map<PropertyIdentifier, Encodable> getProperties(LocalDevice localDevice, RemoteDevice d, RequestListener callback, List<ObjectPropertyReference> refs) throws BACnetException { List<Pair<ObjectPropertyReference, Encodable>> values = readProperties(localDevice, d, refs, callback); Map<PropertyIdentifier, Encodable> map = new HashMap<PropertyIdentifier, Encodable>(values.size()); for (Pair<ObjectPropertyReference, Encodable> pair : values) map.put(pair.getLeft().getPropertyIdentifier(), pair.getRight()); return map; } public static Encodable sendReadPropertyAllowNull(LocalDevice localDevice, RemoteDevice d, ObjectIdentifier oid, PropertyIdentifier pid) throws BACnetException { return sendReadPropertyAllowNull(localDevice, d, oid, pid, null, null); } /** * Sends a ReadProperty-Request and ignores Error responses where the class is Property and the code is * unknownProperty. Returns null in this case. */ public static Encodable sendReadPropertyAllowNull(LocalDevice localDevice, RemoteDevice d, ObjectIdentifier oid, PropertyIdentifier pid, UnsignedInteger propertyArrayIndex, RequestListener callback) throws BACnetException { try { ReadPropertyAck ack = (ReadPropertyAck) localDevice.send(d, new ReadPropertyRequest(oid, pid, propertyArrayIndex)); if (callback != null) callback.requestProgress(1, oid, pid, propertyArrayIndex, ack.getValue()); return ack.getValue(); } catch (AbortAPDUException e) { if (e.getApdu().getAbortReason() == AbortReason.bufferOverflow.intValue() || e.getApdu().getAbortReason() == AbortReason.segmentationNotSupported.intValue()) { // The response may be too long to send. If the property is a sequence... if (ObjectProperties.getPropertyTypeDefinition(oid.getObjectType(), pid).isSequence()) { LOG.info("Received abort exception on sequence request. Sending chunked reference request instead"); // ... then try getting it by sending requests for indices. Find out how many there are. int len = ((UnsignedInteger) sendReadPropertyAllowNull(localDevice, d, oid, pid, new UnsignedInteger(0), null)).intValue(); // Create a list of individual property references. PropertyReferences refs = new PropertyReferences(); for (int i = 1; i <= len; i++) refs.add(oid, new PropertyReference(pid, new UnsignedInteger(i))); // Send the request. Use the method that automatically partitions the request. PropertyValues pvs = readProperties(localDevice, d, refs, callback); // We know that the original request property was a sequence, so create one to store the result. SequenceOf<Encodable> list = new SequenceOf<Encodable>(); for (int i = 1; i <= len; i++) list.add(pvs.getNoErrorCheck(oid, new PropertyReference(pid, new UnsignedInteger(i)))); // And there you go. return list; } throw e; } throw e; } catch (ErrorAPDUException e) { if (e.getBACnetError().equals(ErrorClass.property, ErrorCode.unknownProperty)) return null; throw e; } } public static void getExtendedDeviceInformation(LocalDevice localDevice, RemoteDevice d) throws BACnetException { ObjectIdentifier oid = d.getObjectIdentifier(); // Get the device's supported services if (d.getServicesSupported() == null) { ReadPropertyAck supportedServicesAck = (ReadPropertyAck) localDevice.send(d, new ReadPropertyRequest(oid, PropertyIdentifier.protocolServicesSupported)); d.setServicesSupported((ServicesSupported) supportedServicesAck.getValue()); } // Uses the readProperties method here because this list will probably be extended. PropertyReferences properties = new PropertyReferences(); properties.add(oid, PropertyIdentifier.objectName); properties.add(oid, PropertyIdentifier.protocolVersion); // properties.add(oid, PropertyIdentifier.protocolRevision); PropertyValues values = readProperties(localDevice, d, properties, null); d.setName(values.getString(oid, PropertyIdentifier.objectName)); d.setProtocolVersion((UnsignedInteger) values.getNullOnError(oid, PropertyIdentifier.protocolVersion)); // d.setProtocolRevision((UnsignedInteger) values.getNullOnError(oid, PropertyIdentifier.protocolRevision)); } /** * This version of the readProperties method will preserve the order of properties given in the list in the results. * * @param d * the device to which to send the request * @param oprs * the list of property references to request * @return a list of the original property reference objects wrapped with their values * @throws BACnetException */ public static List<Pair<ObjectPropertyReference, Encodable>> readProperties(LocalDevice localDevice, RemoteDevice d, List<ObjectPropertyReference> oprs, RequestListener callback) throws BACnetException { PropertyReferences refs = new PropertyReferences(); for (ObjectPropertyReference opr : oprs) refs.add(opr.getObjectIdentifier(), opr.getPropertyIdentifier()); PropertyValues pvs = readProperties(localDevice, d, refs, callback); // Read the properties in the same order. List<Pair<ObjectPropertyReference, Encodable>> results = new ArrayList<Pair<ObjectPropertyReference, Encodable>>(); for (ObjectPropertyReference opr : oprs) results.add(new ImmutablePair<ObjectPropertyReference, Encodable>(opr, pvs.getNoErrorCheck(opr))); return results; } public static PropertyValues readProperties(LocalDevice localDevice, RemoteDevice d, PropertyReferences refs, RequestListener callback) throws BACnetException { Map<ObjectIdentifier, List<PropertyReference>> properties; PropertyValues propertyValues = new PropertyValues(); RequestListenerUpdater updater = new RequestListenerUpdater(callback, propertyValues, refs.size()); boolean multipleSupported = d.getServicesSupported() != null && d.getServicesSupported().isReadPropertyMultiple(); boolean forceMultiple = false; // Check if a "special" property identifier is contained in the references. for (List<PropertyReference> prs : refs.getProperties().values()) { for (PropertyReference pr : prs) { PropertyIdentifier pi = pr.getPropertyIdentifier(); if (pi.equals(PropertyIdentifier.all) || pi.equals(PropertyIdentifier.required) || pi.equals(PropertyIdentifier.optional)) { forceMultiple = true; break; } } if (forceMultiple) break; } if (forceMultiple && !multipleSupported) throw new BACnetException("Cannot send request. ReadPropertyMultiple is required but not supported."); if (forceMultiple || (refs.size() > 1 && multipleSupported)) { // Read property multiple can be used. Determine the max references int maxRef = d.getMaxReadMultipleReferences(); // If the device supports read property multiple, send them all at once, or at least in partitions. List<PropertyReferences> partitions = refs.getPropertiesPartitioned(maxRef); int counter = 0; for (PropertyReferences partition : partitions) { properties = partition.getProperties(); List<ReadAccessSpecification> specs = new ArrayList<ReadAccessSpecification>(); for (ObjectIdentifier oid : properties.keySet()) specs.add(new ReadAccessSpecification(oid, new SequenceOf<PropertyReference>(properties.get(oid)))); ReadPropertyMultipleRequest request = new ReadPropertyMultipleRequest( new SequenceOf<ReadAccessSpecification>(specs)); ReadPropertyMultipleAck ack; try { ack = (ReadPropertyMultipleAck) localDevice.send(d, request); counter++; List<ReadAccessResult> results = ack.getListOfReadAccessResults().getValues(); ObjectIdentifier oid; for (ReadAccessResult objectResult : results) { oid = objectResult.getObjectIdentifier(); for (Result result : objectResult.getListOfResults().getValues()) { updater.increment(oid, result.getPropertyIdentifier(), result.getPropertyArrayIndex(), result.getReadResult().getDatum()); if (updater.cancelled()) break; } if (updater.cancelled()) break; } } catch (AbortAPDUException e) { LOG.warning("Chunked request failed."); if (e.getApdu().getAbortReason() == AbortReason.bufferOverflow.intValue() || e.getApdu().getAbortReason() == AbortReason.segmentationNotSupported.intValue()) { if (counter > 0) sendOneAtATime(localDevice, d, partition, updater); else { // Failed on the first partition. Send all one at a time, reduce the device's max // references, and quit. sendOneAtATime(localDevice, d, refs, updater); d.reduceMaxReadMultipleReferences(); break; } } else throw new BACnetException("Completed " + counter + " requests. Excepted on: " + request, e); } catch (BACnetTimeoutException e) { BACnetError error = new BACnetError(ErrorClass.communication, ErrorCode.timeout); for (ObjectIdentifier oid : properties.keySet()) { for (PropertyReference ref : properties.get(oid)) updater.increment(oid, ref.getPropertyIdentifier(), ref.getPropertyArrayIndex(), error); } } catch (BACnetException e) { throw new BACnetException("Completed " + counter + " requests. Excepted on: " + request, e); } if (updater.cancelled()) break; } } else // If it doesn't support read property multiple, send them one at a time. sendOneAtATime(localDevice, d, refs, updater); return propertyValues; } private static void sendOneAtATime(LocalDevice localDevice, RemoteDevice d, PropertyReferences refs, RequestListenerUpdater updater) throws BACnetException { LOG.info("Making property reference requests one at a time"); List<PropertyReference> refList; ReadPropertyRequest request; ReadPropertyAck ack; Map<ObjectIdentifier, List<PropertyReference>> properties = refs.getProperties(); for (ObjectIdentifier oid : properties.keySet()) { refList = properties.get(oid); for (PropertyReference ref : refList) { request = new ReadPropertyRequest(oid, ref.getPropertyIdentifier(), ref.getPropertyArrayIndex()); try { ack = (ReadPropertyAck) localDevice.send(d, request); updater.increment(oid, ack.getPropertyIdentifier(), ack.getPropertyArrayIndex(), ack.getValue()); } catch (BACnetTimeoutException e) { updater.increment(oid, ref.getPropertyIdentifier(), ref.getPropertyArrayIndex(), new BACnetError( ErrorClass.communication, ErrorCode.timeout)); } catch (ErrorAPDUException e) { updater.increment(oid, ref.getPropertyIdentifier(), ref.getPropertyArrayIndex(), e.getBACnetError()); } if (updater.cancelled()) break; } if (updater.cancelled()) break; } } public static PropertyValues readPresentValues(LocalDevice localDevice, RemoteDevice d, RequestListener callback) throws BACnetException { return readPresentValues(localDevice, d, d.getObjects(), callback); } public static PropertyValues readPresentValues(LocalDevice localDevice, RemoteDevice d, List<RemoteObject> objs, RequestListener callback) throws BACnetException { List<ObjectIdentifier> oids = new ArrayList<ObjectIdentifier>(objs.size()); for (RemoteObject o : d.getObjects()) oids.add(o.getObjectIdentifier()); return readOidPresentValues(localDevice, d, oids, callback); } public static PropertyValues readOidPresentValues(LocalDevice localDevice, RemoteDevice d, List<ObjectIdentifier> oids, RequestListener callback) throws BACnetException { if (oids.size() == 0) return new PropertyValues(); PropertyReferences refs = new PropertyReferences(); for (ObjectIdentifier oid : oids) refs.add(oid, PropertyIdentifier.presentValue); return readProperties(localDevice, d, refs, callback); } public static void setProperty(LocalDevice localDevice, RemoteDevice d, ObjectIdentifier oid, PropertyIdentifier pid, Encodable value) throws BACnetException { localDevice.send(d, new WritePropertyRequest(oid, pid, null, value, null)); } public static void setPresentValue(LocalDevice localDevice, RemoteDevice d, ObjectIdentifier oid, Encodable value) throws BACnetException { setProperty(localDevice, d, oid, PropertyIdentifier.presentValue, value); } }