ModbusMaster.cs 17 KB

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