SiemensFetchWriteNet.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Net.Sockets;
  6. using MECF.Framework.RT.Core.IoProviders.Siemens.IMessage;
  7. using MECF.Framework.RT.Core.IoProviders.Siemens.Net.NetworkBase;
  8. using MECF.Framework.RT.Core.IoProviders.Siemens.Transfer;
  9. /********************************************************************************
  10. *
  11. * 说明:西门子通讯类,使用Fetch/Write消息解析规格,和反字节转换规格来实现的
  12. *
  13. * 继承自统一的自定义方法,需要在PLC端进行相关的数据配置
  14. *
  15. *
  16. *********************************************************************************/
  17. namespace MECF.Framework.RT.Core.IoProviders.Siemens
  18. {
  19. /// <summary>
  20. /// 使用了Fetch/Write协议来和西门子进行通讯,该种方法需要在PLC侧进行一些配置 ->
  21. /// Using the Fetch/write protocol to communicate with Siemens, this method requires some configuration on the PLC side
  22. /// </summary>
  23. /// <remarks>
  24. /// 与S7协议相比较而言,本协议不支持对单个的点位的读写操作。如果读取M100.0,需要读取M100的值,然后进行提取位数据
  25. /// 地址支持的列表如下:
  26. /// <list type="table">
  27. /// <listheader>
  28. /// <term>地址名称</term>
  29. /// <term>地址代号</term>
  30. /// <term>示例</term>
  31. /// <term>地址进制</term>
  32. /// <term>字操作</term>
  33. /// <term>位操作</term>
  34. /// <term>备注</term>
  35. /// </listheader>
  36. /// <item>
  37. /// <term>中间寄存器</term>
  38. /// <term>M</term>
  39. /// <term>M100,M200</term>
  40. /// <term>10</term>
  41. /// <term>√</term>
  42. /// <term>×</term>
  43. /// <term></term>
  44. /// </item>
  45. /// <item>
  46. /// <term>输入寄存器</term>
  47. /// <term>I</term>
  48. /// <term>I100,I200</term>
  49. /// <term>10</term>
  50. /// <term>√</term>
  51. /// <term>×</term>
  52. /// <term></term>
  53. /// </item>
  54. /// <item>
  55. /// <term>输出寄存器</term>
  56. /// <term>Q</term>
  57. /// <term>Q100,Q200</term>
  58. /// <term>10</term>
  59. /// <term>√</term>
  60. /// <term>×</term>
  61. /// <term></term>
  62. /// </item>
  63. /// <item>
  64. /// <term>DB块寄存器</term>
  65. /// <term>DB</term>
  66. /// <term>DB1.100,DB1.200</term>
  67. /// <term>10</term>
  68. /// <term>√</term>
  69. /// <term>×</term>
  70. /// <term></term>
  71. /// </item>
  72. /// <item>
  73. /// <term>定时器的值</term>
  74. /// <term>T</term>
  75. /// <term>T100,T200</term>
  76. /// <term>10</term>
  77. /// <term>√</term>
  78. /// <term>×</term>
  79. /// <term></term>
  80. /// </item>
  81. /// <item>
  82. /// <term>计数器的值</term>
  83. /// <term>C</term>
  84. /// <term>C100,C200</term>
  85. /// <term>10</term>
  86. /// <term>√</term>
  87. /// <term>×</term>
  88. /// <term></term>
  89. /// </item>
  90. /// </list>
  91. /// </remarks>
  92. /// <example>
  93. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\SiemensFetchWriteNet.cs" region="Usage" title="简单的短连接使用" />
  94. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\SiemensFetchWriteNet.cs" region="Usage2" title="简单的长连接使用" />
  95. /// </example>
  96. public class SiemensFetchWriteNet : NetworkDeviceBase<FetchWriteMessage, ReverseBytesTransform>
  97. {
  98. #region Constructor
  99. /// <summary>
  100. /// 实例化一个西门子的Fetch/Write协议的通讯对象 ->
  101. /// Instantiate a communication object for a Siemens Fetch/write protocol
  102. /// </summary>
  103. public SiemensFetchWriteNet()
  104. {
  105. WordLength = 2;
  106. }
  107. /// <summary>
  108. /// 实例化一个西门子的Fetch/Write协议的通讯对象 ->
  109. /// Instantiate a communication object for a Siemens Fetch/write protocol
  110. /// </summary>
  111. /// <param name="ipAddress">PLC的Ip地址 -> Specify IP Address</param>
  112. /// <param name="port">PLC的端口 -> Specify IP Port</param>
  113. public SiemensFetchWriteNet(string ipAddress,int port)
  114. {
  115. WordLength = 2;
  116. IpAddress = ipAddress;
  117. Port = port;
  118. }
  119. #endregion
  120. #region Read Support
  121. /// <summary>
  122. /// 从PLC读取数据,地址格式为I100,Q100,DB20.100,M100,T100,C100,以字节为单位 ->
  123. /// Read data from PLC, address format I100,Q100,DB20.100,M100,T100,C100, in bytes
  124. /// </summary>
  125. /// <param name="address">起始地址,格式为I100,M100,Q100,DB20.100,T100,C100 ->
  126. /// Starting address, formatted as I100,M100,Q100,DB20.100,T100,C100
  127. /// </param>
  128. /// <param name="length">读取的数量,以字节为单位 -> The number of reads, in bytes</param>
  129. /// <returns>带有成功标志的字节信息 -> Byte information with a success flag</returns>
  130. /// <example>
  131. /// 假设起始地址为M100,M100存储了温度,100.6℃值为1006,M102存储了压力,1.23Mpa值为123,M104,M105,M106,M107存储了产量计数,读取如下:
  132. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\SiemensFetchWriteNet.cs" region="ReadExample2" title="Read示例" />
  133. /// 以下是读取不同类型数据的示例
  134. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\SiemensFetchWriteNet.cs" region="ReadExample1" title="Read示例" />
  135. /// </example>
  136. public override OperateResult<byte[]> Read( string address, ushort length )
  137. {
  138. // 指令解析 -> Instruction parsing
  139. OperateResult<byte[]> command = BuildReadCommand( address, length );
  140. if (!command.IsSuccess) return command;
  141. // 核心交互 -> Core Interactions
  142. OperateResult<byte[]> read = ReadFromCoreServer( command.Content );
  143. if (!read.IsSuccess) return read;
  144. // 错误码验证 -> Error code Verification
  145. if (read.Content[8] != 0x00) return new OperateResult<byte[]>( read.Content[8], StringResources.Language.SiemensFWError );
  146. // 读取正确 -> Read Right
  147. byte[] buffer = new byte[read.Content.Length - 16];
  148. Array.Copy( read.Content, 16, buffer, 0, buffer.Length );
  149. return OperateResult.CreateSuccessResult( buffer );
  150. }
  151. /// <summary>
  152. /// 读取指定地址的byte数据 -> Reads the byte data for the specified address
  153. /// </summary>
  154. /// <param name="address">起始地址,格式为M100,I100,Q100,DB1.100 -> Starting address, formatted as M100,I100,Q100,DB1.100</param>
  155. /// <returns>byte类型的结果对象 -> Result object of type Byte</returns>
  156. /// <remarks>
  157. /// <note type="warning">
  158. /// 不适用于DB块,定时器,计数器的数据读取,会提示相应的错误,读取长度必须为偶数
  159. /// </note>
  160. /// </remarks>
  161. public OperateResult<byte> ReadByte(string address)
  162. {
  163. return ByteTransformHelper.GetResultFromArray( Read( address, 1 ) );
  164. }
  165. #endregion
  166. #region Write Base
  167. /// <summary>
  168. /// 将数据写入到PLC数据,地址格式为I100,Q100,DB20.100,M100,以字节为单位 ->
  169. /// Writes data to the PLC data, in the address format i100,q100,db20.100,m100, in bytes
  170. /// </summary>
  171. /// <param name="address">起始地址,格式为M100,I100,Q100,DB1.100 -> Starting address, formatted as M100,I100,Q100,DB1.100</param>
  172. /// <param name="value">要写入的实际数据 -> The actual data to write</param>
  173. /// <returns>是否写入成功的结果对象 -> Whether to write a successful result object</returns>
  174. /// <example>
  175. /// 假设起始地址为M100,M100,M101存储了温度,100.6℃值为1006,M102,M103存储了压力,1.23Mpa值为123,M104-M107存储了产量计数,写入如下:
  176. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\SiemensFetchWriteNet.cs" region="WriteExample2" title="Write示例" />
  177. /// 以下是写入不同类型数据的示例
  178. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\SiemensFetchWriteNet.cs" region="WriteExample1" title="Write示例" />
  179. /// </example>
  180. public override OperateResult Write( string address, byte[] value )
  181. {
  182. // 指令解析 -> Instruction parsing
  183. OperateResult<byte[]> command = BuildWriteCommand( address, value );
  184. if (!command.IsSuccess) return command;
  185. // 核心交互 -> Core Interactions
  186. OperateResult<byte[]> write = ReadFromCoreServer( command.Content );
  187. if (!write.IsSuccess) return write;
  188. // 错误码验证 -> Error code Verification
  189. if (write.Content[8] != 0x00) return new OperateResult( write.Content[8], StringResources.Language.SiemensWriteError + write.Content[8] );
  190. // 写入成功 -> Write Right
  191. return OperateResult.CreateSuccessResult( );
  192. }
  193. #endregion
  194. #region Write bool[]
  195. /// <summary>
  196. /// 向PLC中写入bool数组,返回值说明,比如你写入M100,那么data[0]对应M100.0 ->
  197. /// Write the bool array to the PLC, return the value description, for example, if you write M100, then data[0] corresponds to M100.0
  198. /// </summary>
  199. /// <param name="address">起始地址,格式为M100,I100,Q100,DB1.100 -> Starting address, formatted as M100,I100,Q100,DB1.100</param>
  200. /// <param name="values">要写入的实际数据,长度为8的倍数 -> The actual data to write, a multiple of 8 in length</param>
  201. /// <returns>是否写入成功的结果对象 -> Whether to write a successful result object</returns>
  202. /// <remarks>
  203. /// <note type="warning">
  204. /// 批量写入bool数组存在一定的风险,原因是只能批量写入长度为8的倍数的数组,否则会影响其他的位的数据,请谨慎使用。 ->
  205. /// There is a risk in bulk writing to a bool array, because it is possible to write arrays of multiples of length 8 in bulk, otherwise it will affect the data of other bits, please use sparingly.
  206. /// </note>
  207. /// </remarks>
  208. public OperateResult Write(string address, bool[] values)
  209. {
  210. return Write( address, SoftBasic.BoolArrayToByte( values ) );
  211. }
  212. #endregion
  213. #region Write Byte
  214. /// <summary>
  215. /// 向PLC中写入byte数据,返回是否写入成功 -> Writes byte data to the PLC and returns whether the write succeeded
  216. /// </summary>
  217. /// <param name="address">起始地址,格式为M100,I100,Q100,DB1.100 -> Starting address, formatted as M100,I100,Q100,DB1.100</param>
  218. /// <param name="value">要写入的实际数据 -> The actual data to write</param>
  219. /// <returns>是否写入成功的结果对象 -> Whether to write a successful result object</returns>
  220. public OperateResult Write(string address, byte value)
  221. {
  222. return Write( address, new byte[] { value } );
  223. }
  224. #endregion
  225. #region Object Override
  226. /// <summary>
  227. /// 返回表示当前对象的字符串 -> Returns a String representing the current object
  228. /// </summary>
  229. /// <returns>字符串数据 -> String data</returns>
  230. public override string ToString()
  231. {
  232. return $"SiemensFetchWriteNet[{IpAddress}:{Port}]";
  233. }
  234. #endregion
  235. #region Static Method Helper
  236. /// <summary>
  237. /// 计算特殊的地址信息
  238. /// </summary>
  239. /// <param name="address">字符串信息</param>
  240. /// <returns>实际值</returns>
  241. private static int CalculateAddressStarted( string address )
  242. {
  243. if (address.IndexOf( '.' ) < 0)
  244. {
  245. return Convert.ToInt32( address );
  246. }
  247. else
  248. {
  249. string[] temp = address.Split( '.' );
  250. return Convert.ToInt32( temp[0] );
  251. }
  252. }
  253. /// <summary>
  254. /// 解析数据地址,解析出地址类型,起始地址,DB块的地址 -> Parse data address, parse out address type, start address, db block address
  255. /// </summary>
  256. /// <param name="address">起始地址,格式为M100,I100,Q100,DB1.100 -> Starting address, formatted as M100,I100,Q100,DB1.100</param>
  257. /// <returns>解析出地址类型,起始地址,DB块的地址 -> Resolves address type, start address, db block address</returns>
  258. private static OperateResult<byte, int, ushort> AnalysisAddress( string address )
  259. {
  260. var result = new OperateResult<byte, int, ushort>( );
  261. try
  262. {
  263. result.Content3 = 0;
  264. if (address[0] == 'I')
  265. {
  266. result.Content1 = 0x03;
  267. result.Content2 = CalculateAddressStarted( address.Substring( 1 ) );
  268. }
  269. else if (address[0] == 'Q')
  270. {
  271. result.Content1 = 0x04;
  272. result.Content2 = CalculateAddressStarted( address.Substring( 1 ) );
  273. }
  274. else if (address[0] == 'M')
  275. {
  276. result.Content1 = 0x02;
  277. result.Content2 = CalculateAddressStarted( address.Substring( 1 ) );
  278. }
  279. else if (address[0] == 'D' || address.Substring( 0, 2 ) == "DB")
  280. {
  281. result.Content1 = 0x01;
  282. string[] adds = address.Split( '.' );
  283. if (address[1] == 'B')
  284. {
  285. result.Content3 = Convert.ToUInt16( adds[0].Substring( 2 ) );
  286. }
  287. else
  288. {
  289. result.Content3 = Convert.ToUInt16( adds[0].Substring( 1 ) );
  290. }
  291. if (result.Content3 > 255)
  292. {
  293. result.Message = StringResources.Language.SiemensDBAddressNotAllowedLargerThan255;
  294. return result;
  295. }
  296. result.Content2 = CalculateAddressStarted( address.Substring( address.IndexOf( '.' ) + 1 ) );
  297. }
  298. else if (address[0] == 'T')
  299. {
  300. result.Content1 = 0x07;
  301. result.Content2 = CalculateAddressStarted( address.Substring( 1 ) );
  302. }
  303. else if (address[0] == 'C')
  304. {
  305. result.Content1 = 0x06;
  306. result.Content2 = CalculateAddressStarted( address.Substring( 1 ) );
  307. }
  308. else
  309. {
  310. result.Message = StringResources.Language.NotSupportedDataType;
  311. result.Content1 = 0;
  312. result.Content2 = 0;
  313. result.Content3 = 0;
  314. return result;
  315. }
  316. }
  317. catch (Exception ex)
  318. {
  319. result.Message = ex.Message;
  320. return result;
  321. }
  322. result.IsSuccess = true;
  323. return result;
  324. }
  325. #endregion
  326. #region Build Command
  327. /// <summary>
  328. /// 生成一个读取字数据指令头的通用方法 -> A general method for generating a command header to read a Word data
  329. /// </summary>
  330. /// <param name="address">起始地址,格式为M100,I100,Q100,DB1.100 -> Starting address, formatted as M100,I100,Q100,DB1.100</param>
  331. /// <param name="count">读取数据个数 -> Number of Read data</param>
  332. /// <returns>带结果对象的报文数据 -> Message data with a result object</returns>
  333. public static OperateResult<byte[]> BuildReadCommand( string address, ushort count )
  334. {
  335. OperateResult<byte, int, ushort> analysis = AnalysisAddress( address );
  336. if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( analysis );
  337. byte[] _PLCCommand = new byte[16];
  338. _PLCCommand[0] = 0x53;
  339. _PLCCommand[1] = 0x35;
  340. _PLCCommand[2] = 0x10;
  341. _PLCCommand[3] = 0x01;
  342. _PLCCommand[4] = 0x03;
  343. _PLCCommand[5] = 0x05;
  344. _PLCCommand[6] = 0x03;
  345. _PLCCommand[7] = 0x08;
  346. // 指定数据区 -> Specify Data area
  347. _PLCCommand[8] = analysis.Content1;
  348. _PLCCommand[9] = (byte)analysis.Content3;
  349. // 指定数据地址 -> Specify Data address
  350. _PLCCommand[10] = (byte)(analysis.Content2 / 256);
  351. _PLCCommand[11] = (byte)(analysis.Content2 % 256);
  352. // DB块,定时器,计数器读取长度按照字为单位,1代表2个字节,I,Q,M的1代表1个字节 ->
  353. // DB block, timer, counter read length per word, 1 for 2 bytes, i,q,m 1 for 1 bytes
  354. if (analysis.Content1 == 0x01 || analysis.Content1 == 0x06 || analysis.Content1 == 0x07)
  355. {
  356. if (count % 2 != 0)
  357. {
  358. return new OperateResult<byte[]>( StringResources.Language.SiemensReadLengthMustBeEvenNumber );
  359. }
  360. else
  361. {
  362. // 指定数据长度 -> Specify data length
  363. _PLCCommand[12] = (byte)(count / 2 / 256);
  364. _PLCCommand[13] = (byte)(count / 2 % 256);
  365. }
  366. }
  367. else
  368. {
  369. // 指定数据长度 -> Specify data length
  370. _PLCCommand[12] = (byte)(count / 256);
  371. _PLCCommand[13] = (byte)(count % 256);
  372. }
  373. _PLCCommand[14] = 0xff;
  374. _PLCCommand[15] = 0x02;
  375. return OperateResult.CreateSuccessResult( _PLCCommand );
  376. }
  377. /// <summary>
  378. /// 生成一个写入字节数据的指令 -> Generate an instruction to write byte data
  379. /// </summary>
  380. /// <param name="address">起始地址,格式为M100,I100,Q100,DB1.100 -> Starting address, formatted as M100,I100,Q100,DB1.100</param>
  381. /// <param name="data">实际的写入的内容 -> The actual content of the write</param>
  382. /// <returns>带结果对象的报文数据 -> Message data with a result object</returns>
  383. public static OperateResult<byte[]> BuildWriteCommand( string address, byte[] data )
  384. {
  385. if (data == null) data = new byte[0];
  386. OperateResult<byte, int, ushort> analysis = AnalysisAddress( address );
  387. if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( analysis );
  388. byte[] _PLCCommand = new byte[16 + data.Length];
  389. _PLCCommand[0] = 0x53;
  390. _PLCCommand[1] = 0x35;
  391. _PLCCommand[2] = 0x10;
  392. _PLCCommand[3] = 0x01;
  393. _PLCCommand[4] = 0x03;
  394. _PLCCommand[5] = 0x03;
  395. _PLCCommand[6] = 0x03;
  396. _PLCCommand[7] = 0x08;
  397. // 指定数据区 -> Specify Data area
  398. _PLCCommand[8] = analysis.Content1;
  399. _PLCCommand[9] = (byte)analysis.Content3;
  400. // 指定数据地址 -> Specify Data address
  401. _PLCCommand[10] = (byte)(analysis.Content2 / 256);
  402. _PLCCommand[11] = (byte)(analysis.Content2 % 256);
  403. if (analysis.Content1 == 0x01 || analysis.Content1 == 0x06 || analysis.Content1 == 0x07)
  404. {
  405. if (data.Length % 2 != 0)
  406. {
  407. return new OperateResult<byte[]>( StringResources.Language.SiemensReadLengthMustBeEvenNumber );
  408. }
  409. else
  410. {
  411. // 指定数据长度 -> Specify data length
  412. _PLCCommand[12] = (byte)(data.Length / 2 / 256);
  413. _PLCCommand[13] = (byte)(data.Length / 2 % 256);
  414. }
  415. }
  416. else
  417. {
  418. // 指定数据长度 -> Specify data length
  419. _PLCCommand[12] = (byte)(data.Length / 256);
  420. _PLCCommand[13] = (byte)(data.Length % 256);
  421. }
  422. _PLCCommand[14] = 0xff;
  423. _PLCCommand[15] = 0x02;
  424. // 放置数据 -> Placing data
  425. Array.Copy( data, 0, _PLCCommand, 16, data.Length );
  426. return OperateResult.CreateSuccessResult( _PLCCommand );
  427. }
  428. #endregion
  429. }
  430. }