ModbusMaster.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. using System;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using NModbus.Data;
  6. using NModbus.Message;
  7. namespace NModbus.Device
  8. {
  9. /// <summary>
  10. /// Modbus master device.
  11. /// </summary>
  12. public abstract class ModbusMaster : ModbusDevice, IModbusMaster
  13. {
  14. protected ModbusMaster(IModbusTransport transport)
  15. : base(transport)
  16. {
  17. }
  18. /// <summary>
  19. /// Reads from 1 to 2000 contiguous coils status.
  20. /// </summary>
  21. /// <param name="slaveAddress">Address of device to read values from.</param>
  22. /// <param name="startAddress">Address to begin reading.</param>
  23. /// <param name="numberOfPoints">Number of coils to read.</param>
  24. /// <returns>Coils status.</returns>
  25. public bool[] ReadCoils(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  26. {
  27. ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 2000);
  28. var request = new ReadCoilsInputsRequest(
  29. ModbusFunctionCodes.ReadCoils,
  30. slaveAddress,
  31. startAddress,
  32. numberOfPoints);
  33. return PerformReadDiscretes(request);
  34. }
  35. /// <summary>
  36. /// Asynchronously reads from 1 to 2000 contiguous coils status.
  37. /// </summary>
  38. /// <param name="slaveAddress">Address of device to read values from.</param>
  39. /// <param name="startAddress">Address to begin reading.</param>
  40. /// <param name="numberOfPoints">Number of coils to read.</param>
  41. /// <returns>A task that represents the asynchronous read operation.</returns>
  42. public Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  43. {
  44. ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 2000);
  45. var request = new ReadCoilsInputsRequest(
  46. ModbusFunctionCodes.ReadCoils,
  47. slaveAddress,
  48. startAddress,
  49. numberOfPoints);
  50. return PerformReadDiscretesAsync(request);
  51. }
  52. /// <summary>
  53. /// Reads from 1 to 2000 contiguous discrete input status.
  54. /// </summary>
  55. /// <param name="slaveAddress">Address of device to read values from.</param>
  56. /// <param name="startAddress">Address to begin reading.</param>
  57. /// <param name="numberOfPoints">Number of discrete inputs to read.</param>
  58. /// <returns>Discrete inputs status.</returns>
  59. public bool[] ReadInputs(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  60. {
  61. ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 2000);
  62. var request = new ReadCoilsInputsRequest(
  63. ModbusFunctionCodes.ReadInputs,
  64. slaveAddress,
  65. startAddress,
  66. numberOfPoints);
  67. return PerformReadDiscretes(request);
  68. }
  69. /// <summary>
  70. /// Asynchronously reads from 1 to 2000 contiguous discrete input status.
  71. /// </summary>
  72. /// <param name="slaveAddress">Address of device to read values from.</param>
  73. /// <param name="startAddress">Address to begin reading.</param>
  74. /// <param name="numberOfPoints">Number of discrete inputs to read.</param>
  75. /// <returns>A task that represents the asynchronous read operation.</returns>
  76. public Task<bool[]> ReadInputsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  77. {
  78. ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 2000);
  79. var request = new ReadCoilsInputsRequest(
  80. ModbusFunctionCodes.ReadInputs,
  81. slaveAddress,
  82. startAddress,
  83. numberOfPoints);
  84. return PerformReadDiscretesAsync(request);
  85. }
  86. /// <summary>
  87. /// Reads contiguous block of holding registers.
  88. /// </summary>
  89. /// <param name="slaveAddress">Address of device to read values from.</param>
  90. /// <param name="startAddress">Address to begin reading.</param>
  91. /// <param name="numberOfPoints">Number of holding registers to read.</param>
  92. /// <returns>Holding registers status.</returns>
  93. public ushort[] ReadHoldingRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  94. {
  95. ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 125);
  96. var request = new ReadHoldingInputRegistersRequest(
  97. ModbusFunctionCodes.ReadHoldingRegisters,
  98. slaveAddress,
  99. startAddress,
  100. numberOfPoints);
  101. return PerformReadRegisters(request);
  102. }
  103. /// <summary>
  104. /// Asynchronously reads contiguous block of holding registers.
  105. /// </summary>
  106. /// <param name="slaveAddress">Address of device to read values from.</param>
  107. /// <param name="startAddress">Address to begin reading.</param>
  108. /// <param name="numberOfPoints">Number of holding registers to read.</param>
  109. /// <returns>A task that represents the asynchronous read operation.</returns>
  110. public Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  111. {
  112. ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 125);
  113. var request = new ReadHoldingInputRegistersRequest(
  114. ModbusFunctionCodes.ReadHoldingRegisters,
  115. slaveAddress,
  116. startAddress,
  117. numberOfPoints);
  118. return PerformReadRegistersAsync(request);
  119. }
  120. /// <summary>
  121. /// Reads contiguous block of input registers.
  122. /// </summary>
  123. /// <param name="slaveAddress">Address of device to read values from.</param>
  124. /// <param name="startAddress">Address to begin reading.</param>
  125. /// <param name="numberOfPoints">Number of holding registers to read.</param>
  126. /// <returns>Input registers status.</returns>
  127. public ushort[] ReadInputRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  128. {
  129. ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 125);
  130. var request = new ReadHoldingInputRegistersRequest(
  131. ModbusFunctionCodes.ReadInputRegisters,
  132. slaveAddress,
  133. startAddress,
  134. numberOfPoints);
  135. return PerformReadRegisters(request);
  136. }
  137. /// <summary>
  138. /// Asynchronously reads contiguous block of input registers.
  139. /// </summary>
  140. /// <param name="slaveAddress">Address of device to read values from.</param>
  141. /// <param name="startAddress">Address to begin reading.</param>
  142. /// <param name="numberOfPoints">Number of holding registers to read.</param>
  143. /// <returns>A task that represents the asynchronous read operation.</returns>
  144. public Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
  145. {
  146. ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 125);
  147. var request = new ReadHoldingInputRegistersRequest(
  148. ModbusFunctionCodes.ReadInputRegisters,
  149. slaveAddress,
  150. startAddress,
  151. numberOfPoints);
  152. return PerformReadRegistersAsync(request);
  153. }
  154. /// <summary>
  155. /// Writes a single coil value.
  156. /// </summary>
  157. /// <param name="slaveAddress">Address of the device to write to.</param>
  158. /// <param name="coilAddress">Address to write value to.</param>
  159. /// <param name="value">Value to write.</param>
  160. public void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value)
  161. {
  162. var request = new WriteSingleCoilRequestResponse(slaveAddress, coilAddress, value);
  163. Transport.UnicastMessage<WriteSingleCoilRequestResponse>(request);
  164. }
  165. /// <summary>
  166. /// Asynchronously writes a single coil value.
  167. /// </summary>
  168. /// <param name="slaveAddress">Address of the device to write to.</param>
  169. /// <param name="coilAddress">Address to write value to.</param>
  170. /// <param name="value">Value to write.</param>
  171. /// <returns>A task that represents the asynchronous write operation.</returns>
  172. public Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value)
  173. {
  174. var request = new WriteSingleCoilRequestResponse(slaveAddress, coilAddress, value);
  175. return PerformWriteRequestAsync<WriteSingleCoilRequestResponse>(request);
  176. }
  177. /// <summary>
  178. /// Writes a single holding register.
  179. /// </summary>
  180. /// <param name="slaveAddress">Address of the device to write to.</param>
  181. /// <param name="registerAddress">Address to write.</param>
  182. /// <param name="value">Value to write.</param>
  183. public void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value)
  184. {
  185. var request = new WriteSingleRegisterRequestResponse(
  186. slaveAddress,
  187. registerAddress,
  188. value);
  189. Transport.UnicastMessage<WriteSingleRegisterRequestResponse>(request);
  190. }
  191. /// <summary>
  192. /// Asynchronously writes a single holding register.
  193. /// </summary>
  194. /// <param name="slaveAddress">Address of the device to write to.</param>
  195. /// <param name="registerAddress">Address to write.</param>
  196. /// <param name="value">Value to write.</param>
  197. /// <returns>A task that represents the asynchronous write operation.</returns>
  198. public Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value)
  199. {
  200. var request = new WriteSingleRegisterRequestResponse(
  201. slaveAddress,
  202. registerAddress,
  203. value);
  204. return PerformWriteRequestAsync<WriteSingleRegisterRequestResponse>(request);
  205. }
  206. /// <summary>
  207. /// Write a block of 1 to 123 contiguous 16 bit holding registers.
  208. /// </summary>
  209. /// <param name="slaveAddress">Address of the device to write to.</param>
  210. /// <param name="startAddress">Address to begin writing values.</param>
  211. /// <param name="data">Values to write.</param>
  212. public void WriteMultipleRegisters(byte slaveAddress, ushort startAddress, ushort[] data)
  213. {
  214. ValidateData("data", data, 123);
  215. var request = new WriteMultipleRegistersRequest(
  216. slaveAddress,
  217. startAddress,
  218. new RegisterCollection(data));
  219. Transport.UnicastMessage<WriteMultipleRegistersResponse>(request);
  220. }
  221. /// <summary>
  222. /// Asynchronously writes a block of 1 to 123 contiguous registers.
  223. /// </summary>
  224. /// <param name="slaveAddress">Address of the device to write to.</param>
  225. /// <param name="startAddress">Address to begin writing values.</param>
  226. /// <param name="data">Values to write.</param>
  227. /// <returns>A task that represents the asynchronous write operation.</returns>
  228. public Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] data)
  229. {
  230. ValidateData("data", data, 123);
  231. var request = new WriteMultipleRegistersRequest(
  232. slaveAddress,
  233. startAddress,
  234. new RegisterCollection(data));
  235. return PerformWriteRequestAsync<WriteMultipleRegistersResponse>(request);
  236. }
  237. /// <summary>
  238. /// Writes a sequence of coils.
  239. /// </summary>
  240. /// <param name="slaveAddress">Address of the device to write to.</param>
  241. /// <param name="startAddress">Address to begin writing values.</param>
  242. /// <param name="data">Values to write.</param>
  243. public void WriteMultipleCoils(byte slaveAddress, ushort startAddress, bool[] data)
  244. {
  245. ValidateData("data", data, 1968);
  246. var request = new WriteMultipleCoilsRequest(
  247. slaveAddress,
  248. startAddress,
  249. new DiscreteCollection(data));
  250. Transport.UnicastMessage<WriteMultipleCoilsResponse>(request);
  251. }
  252. /// <summary>
  253. /// Asynchronously writes a sequence of coils.
  254. /// </summary>
  255. /// <param name="slaveAddress">Address of the device to write to.</param>
  256. /// <param name="startAddress">Address to begin writing values.</param>
  257. /// <param name="data">Values to write.</param>
  258. /// <returns>A task that represents the asynchronous write operation.</returns>
  259. public Task WriteMultipleCoilsAsync(byte slaveAddress, ushort startAddress, bool[] data)
  260. {
  261. ValidateData("data", data, 1968);
  262. var request = new WriteMultipleCoilsRequest(
  263. slaveAddress,
  264. startAddress,
  265. new DiscreteCollection(data));
  266. return PerformWriteRequestAsync<WriteMultipleCoilsResponse>(request);
  267. }
  268. /// <summary>
  269. /// Performs a combination of one read operation and one write operation in a single Modbus transaction.
  270. /// The write operation is performed before the read.
  271. /// </summary>
  272. /// <param name="slaveAddress">Address of device to read values from.</param>
  273. /// <param name="startReadAddress">Address to begin reading (Holding registers are addressed starting at 0).</param>
  274. /// <param name="numberOfPointsToRead">Number of registers to read.</param>
  275. /// <param name="startWriteAddress">Address to begin writing (Holding registers are addressed starting at 0).</param>
  276. /// <param name="writeData">Register values to write.</param>
  277. public ushort[] ReadWriteMultipleRegisters(
  278. byte slaveAddress,
  279. ushort startReadAddress,
  280. ushort numberOfPointsToRead,
  281. ushort startWriteAddress,
  282. ushort[] writeData)
  283. {
  284. ValidateNumberOfPoints("numberOfPointsToRead", numberOfPointsToRead, 125);
  285. ValidateData("writeData", writeData, 121);
  286. var request = new ReadWriteMultipleRegistersRequest(
  287. slaveAddress,
  288. startReadAddress,
  289. numberOfPointsToRead,
  290. startWriteAddress,
  291. new RegisterCollection(writeData));
  292. return PerformReadRegisters(request);
  293. }
  294. /// <summary>
  295. /// Asynchronously performs a combination of one read operation and one write operation in a single Modbus transaction.
  296. /// The write operation is performed before the read.
  297. /// </summary>
  298. /// <param name="slaveAddress">Address of device to read values from.</param>
  299. /// <param name="startReadAddress">Address to begin reading (Holding registers are addressed starting at 0).</param>
  300. /// <param name="numberOfPointsToRead">Number of registers to read.</param>
  301. /// <param name="startWriteAddress">Address to begin writing (Holding registers are addressed starting at 0).</param>
  302. /// <param name="writeData">Register values to write.</param>
  303. /// <returns>A task that represents the asynchronous operation.</returns>
  304. public Task<ushort[]> ReadWriteMultipleRegistersAsync(
  305. byte slaveAddress,
  306. ushort startReadAddress,
  307. ushort numberOfPointsToRead,
  308. ushort startWriteAddress,
  309. ushort[] writeData)
  310. {
  311. ValidateNumberOfPoints("numberOfPointsToRead", numberOfPointsToRead, 125);
  312. ValidateData("writeData", writeData, 121);
  313. var request = new ReadWriteMultipleRegistersRequest(
  314. slaveAddress,
  315. startReadAddress,
  316. numberOfPointsToRead,
  317. startWriteAddress,
  318. new RegisterCollection(writeData));
  319. return PerformReadRegistersAsync(request);
  320. }
  321. /// <summary>
  322. /// Write a file record to the device.
  323. /// </summary>
  324. /// <param name="slaveAdress">Address of device to write values to</param>
  325. /// <param name="fileNumber">The Extended Memory file number</param>
  326. /// <param name="startingAddress">The starting register address within the file</param>
  327. /// <param name="data">The data to be written</param>
  328. public void WriteFileRecord(byte slaveAdress, ushort fileNumber, ushort startingAddress, byte[] data)
  329. {
  330. ValidateMaxData("data", data, 244);
  331. var request = new WriteFileRecordRequest(slaveAdress, new FileRecordCollection(
  332. fileNumber, startingAddress, data));
  333. Transport.UnicastMessage<WriteFileRecordResponse>(request);
  334. }
  335. /// <summary>
  336. /// Executes the custom message.
  337. /// </summary>
  338. /// <typeparam name="TResponse">The type of the response.</typeparam>
  339. /// <param name="request">The request.</param>
  340. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
  341. [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")]
  342. public TResponse ExecuteCustomMessage<TResponse>(IModbusMessage request)
  343. where TResponse : IModbusMessage, new()
  344. {
  345. return Transport.UnicastMessage<TResponse>(request);
  346. }
  347. private static void ValidateData<T>(string argumentName, T[] data, int maxDataLength)
  348. {
  349. if (data == null)
  350. {
  351. throw new ArgumentNullException(nameof(data));
  352. }
  353. if (data.Length == 0 || data.Length > maxDataLength)
  354. {
  355. string msg = $"The length of argument {argumentName} must be between 1 and {maxDataLength} inclusive.";
  356. throw new ArgumentException(msg);
  357. }
  358. }
  359. private static void ValidateMaxData<T>(string argumentName, T[] data, int maxDataLength)
  360. {
  361. if (data == null)
  362. {
  363. throw new ArgumentNullException(nameof(data));
  364. }
  365. if (data.Length > maxDataLength)
  366. {
  367. string msg = $"The length of argument {argumentName} must not be greater than {maxDataLength}.";
  368. throw new ArgumentException(msg);
  369. }
  370. }
  371. private static void ValidateNumberOfPoints(string argumentName, ushort numberOfPoints, ushort maxNumberOfPoints)
  372. {
  373. if (numberOfPoints < 1 || numberOfPoints > maxNumberOfPoints)
  374. {
  375. string msg = $"Argument {argumentName} must be between 1 and {maxNumberOfPoints} inclusive.";
  376. throw new ArgumentException(msg);
  377. }
  378. }
  379. private bool[] PerformReadDiscretes(ReadCoilsInputsRequest request)
  380. {
  381. ReadCoilsInputsResponse response = Transport.UnicastMessage<ReadCoilsInputsResponse>(request);
  382. return response.Data.Take(request.NumberOfPoints).ToArray();
  383. }
  384. private Task<bool[]> PerformReadDiscretesAsync(ReadCoilsInputsRequest request)
  385. {
  386. return Task.Factory.StartNew(() => PerformReadDiscretes(request));
  387. }
  388. private ushort[] PerformReadRegisters(ReadHoldingInputRegistersRequest request)
  389. {
  390. ReadHoldingInputRegistersResponse response =
  391. Transport.UnicastMessage<ReadHoldingInputRegistersResponse>(request);
  392. return response.Data.Take(request.NumberOfPoints).ToArray();
  393. }
  394. private Task<ushort[]> PerformReadRegistersAsync(ReadHoldingInputRegistersRequest request)
  395. {
  396. return Task.Factory.StartNew(() => PerformReadRegisters(request));
  397. }
  398. private ushort[] PerformReadRegisters(ReadWriteMultipleRegistersRequest request)
  399. {
  400. ReadHoldingInputRegistersResponse response =
  401. Transport.UnicastMessage<ReadHoldingInputRegistersResponse>(request);
  402. return response.Data.Take(request.ReadRequest.NumberOfPoints).ToArray();
  403. }
  404. private Task<ushort[]> PerformReadRegistersAsync(ReadWriteMultipleRegistersRequest request)
  405. {
  406. return Task.Factory.StartNew(() => PerformReadRegisters(request));
  407. }
  408. private Task PerformWriteRequestAsync<T>(IModbusMessage request)
  409. where T : IModbusMessage, new()
  410. {
  411. return Task.Factory.StartNew(() => Transport.UnicastMessage<T>(request));
  412. }
  413. }
  414. }