BatchRead.java 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. /*
  2. * ============================================================================
  3. * GNU General Public License
  4. * ============================================================================
  5. *
  6. * Copyright (C) 2006-2011 Serotonin Software Technologies Inc. http://serotoninsoftware.com
  7. * @author Matthew Lohbihler
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation, either version 3 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. package com.serotonin.cdc.modbus4j;
  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.Collections;
  25. import java.util.Comparator;
  26. import java.util.HashMap;
  27. import java.util.Iterator;
  28. import java.util.List;
  29. import java.util.Map;
  30. import com.serotonin.cdc.modbus4j.base.KeyedModbusLocator;
  31. import com.serotonin.cdc.modbus4j.base.ReadFunctionGroup;
  32. import com.serotonin.cdc.modbus4j.locator.BaseLocator;
  33. /**
  34. * A class for defining the information required to obtain in a batch.
  35. *
  36. * The generic parameterisation represents the class of the key that will be used to find the results in the BatchRead
  37. * object. Typically String would be used, but any Object is valid.
  38. *
  39. * Some modbus devices have non-contiguous sets of values within a single register range. These gaps between values may
  40. * cause the device to return error responses if a request attempts to read them. In spite of this, because it is
  41. * generally more efficient to read a set of values with a single request, the batch read by default will assume that no
  42. * such error responses will be returned. If your batch request results in such errors, it is recommended that you
  43. * separate the offending request to a separate batch read object, or you can use the "contiguous requests" setting
  44. * which causes requests to be partitioned into only contiguous sets.
  45. *
  46. * @author mlohbihler
  47. * @param <K>
  48. */
  49. public class BatchRead {
  50. private final List requestValues = new ArrayList();
  51. /**
  52. * See documentation above.
  53. */
  54. private boolean contiguousRequests = false;
  55. /**
  56. * If this value is false, any error response received will cause an exception to be thrown, and the entire batch to
  57. * be aborted (unless exceptionsInResults is true - see below). If set to true, error responses will be set as the
  58. * result of all affected locators and the entire batch will be attempted with no such exceptions thrown.
  59. */
  60. private boolean errorsInResults = false;
  61. /**
  62. * If this value is false, any exceptions thrown will cause the entire batch to be aborted. If set to true, the
  63. * exception will be set as the result of all affected locators and the entire batch will be attempted with no such
  64. * exceptions thrown.
  65. */
  66. private boolean exceptionsInResults = false;
  67. /**
  68. * This is what the data looks like after partitioning.
  69. */
  70. private List functionGroups;
  71. public boolean isContiguousRequests() {
  72. return contiguousRequests;
  73. }
  74. public void setContiguousRequests(boolean contiguousRequests) {
  75. this.contiguousRequests = contiguousRequests;
  76. functionGroups = null;
  77. }
  78. public boolean isErrorsInResults() {
  79. return errorsInResults;
  80. }
  81. public void setErrorsInResults(boolean errorsInResults) {
  82. this.errorsInResults = errorsInResults;
  83. }
  84. public boolean isExceptionsInResults() {
  85. return exceptionsInResults;
  86. }
  87. public void setExceptionsInResults(boolean exceptionsInResults) {
  88. this.exceptionsInResults = exceptionsInResults;
  89. }
  90. public List getReadFunctionGroups(ModbusMaster master) {
  91. if (functionGroups == null)
  92. doPartition(master);
  93. return functionGroups;
  94. }
  95. public void addLocator(Object id, BaseLocator locator) {
  96. addLocator(new KeyedModbusLocator(id, locator));
  97. }
  98. private void addLocator(KeyedModbusLocator locator) {
  99. requestValues.add(locator);
  100. functionGroups = null;
  101. }
  102. //
  103. // /
  104. // / Private stuff
  105. // /
  106. //
  107. private void doPartition(ModbusMaster master) {
  108. Map slaveRangeBatch = new HashMap();
  109. // Separate the batch into slave ids and read functions.
  110. List functionList;
  111. for (Iterator iter = requestValues.iterator(); iter.hasNext();) {
  112. KeyedModbusLocator locator = (KeyedModbusLocator) iter.next();
  113. // Find the function list for this slave and range. Create it if necessary.
  114. functionList = (List) slaveRangeBatch.get(locator.getSlaveAndRange());
  115. if (functionList == null) {
  116. functionList = new ArrayList();
  117. slaveRangeBatch.put(locator.getSlaveAndRange(), functionList);
  118. }
  119. // Add this locator to the function list.
  120. functionList.add(locator);
  121. }
  122. // Now that we have locators grouped into slave and function, check each read function group and break into
  123. // parts as necessary.
  124. Collection functionLocatorLists = slaveRangeBatch.values();
  125. FunctionLocatorComparator comparator = new FunctionLocatorComparator();
  126. functionGroups = new ArrayList();
  127. for (Iterator iter = functionLocatorLists.iterator(); iter.hasNext();) {
  128. List functionLocatorList = (List) iter.next();
  129. // Sort the list by offset.
  130. Collections.sort(functionLocatorList, comparator);
  131. // Break into parts by excessive request length. Remember the max item count that we can ask for, for
  132. // this function
  133. int maxReadCount = master.getMaxReadCount(((KeyedModbusLocator) functionLocatorList.get(0))
  134. .getSlaveAndRange().getRange());
  135. // Create the request groups.
  136. createRequestGroups(functionGroups, functionLocatorList, maxReadCount);
  137. //System.out.println("requests: " + functionGroups.size());
  138. }
  139. }
  140. /**
  141. * We aren't trying to do anything fancy here, like some kind of artificial optimal group for performance or
  142. * anything. We pretty much just try to fit as many locators as possible into a single valid request, and then move
  143. * on.
  144. *
  145. * This method assumes the locators have already been sorted by start offset.
  146. */
  147. private void createRequestGroups(List functionGroups, List locators, int maxCount) {
  148. ReadFunctionGroup functionGroup;
  149. KeyedModbusLocator locator;
  150. int index;
  151. int endOffset;
  152. // Loop for creation of groups.
  153. while (locators.size() > 0) {
  154. functionGroup = new ReadFunctionGroup((KeyedModbusLocator) locators.remove(0));
  155. functionGroups.add(functionGroup);
  156. endOffset = functionGroup.getStartOffset() + maxCount - 1;
  157. // Loop for adding locators to the current group
  158. index = 0;
  159. while (locators.size() > index) {
  160. locator = (KeyedModbusLocator) locators.get(index);
  161. boolean added = false;
  162. if (locator.getEndOffset() <= endOffset) {
  163. if (contiguousRequests) {
  164. // The locator must at least abut the other locators in the group.
  165. if (locator.getOffset() <= functionGroup.getEndOffset() + 1) {
  166. functionGroup.add((KeyedModbusLocator) locators.remove(index));
  167. added = true;
  168. }
  169. }
  170. else {
  171. functionGroup.add((KeyedModbusLocator) locators.remove(index));
  172. added = true;
  173. }
  174. }
  175. if (!added) {
  176. // This locator does not fit inside the current function...
  177. if (locator.getOffset() > endOffset)
  178. // ... and since the list is sorted by offset, no other locators can either, so quit the loop.
  179. break;
  180. // ... but there still may be other locators that can, so increment the index
  181. index++;
  182. }
  183. }
  184. }
  185. }
  186. class FunctionLocatorComparator implements Comparator {
  187. public int compare(Object ml1, Object ml2) {
  188. return ((KeyedModbusLocator) ml1).getOffset() - ((KeyedModbusLocator) ml2).getOffset();
  189. }
  190. }
  191. }