OmronHostLink.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. using MECF.Framework.RT.Core.IoProviders.Common;
  2. using MECF.Framework.RT.Core.IoProviders.Common.Serial;
  3. using MECF.Framework.RT.Core.IoProviders.Common.Transfer;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. namespace MECF.Framework.RT.Core.IoProviders.Omron
  10. {
  11. /// <summary>
  12. /// 欧姆龙的HostLink协议的实现
  13. /// </summary>
  14. /// <remarks>
  15. /// 感谢 深圳~拾忆 的测试
  16. /// 欧姆龙的地址参考如下:
  17. /// 地址支持的列表如下:
  18. /// <list type="table">
  19. /// <listheader>
  20. /// <term>地址名称</term>
  21. /// <term>地址代号</term>
  22. /// <term>示例</term>
  23. /// <term>地址进制</term>
  24. /// <term>字操作</term>
  25. /// <term>位操作</term>
  26. /// <term>备注</term>
  27. /// </listheader>
  28. /// <item>
  29. /// <term>DM Area</term>
  30. /// <term>D</term>
  31. /// <term>D100,D200</term>
  32. /// <term>10</term>
  33. /// <term>√</term>
  34. /// <term>√</term>
  35. /// <term></term>
  36. /// </item>
  37. /// <item>
  38. /// <term>CIO Area</term>
  39. /// <term>C</term>
  40. /// <term>C100,C200</term>
  41. /// <term>10</term>
  42. /// <term>√</term>
  43. /// <term>√</term>
  44. /// <term></term>
  45. /// </item>
  46. /// <item>
  47. /// <term>Work Area</term>
  48. /// <term>W</term>
  49. /// <term>W100,W200</term>
  50. /// <term>10</term>
  51. /// <term>√</term>
  52. /// <term>√</term>
  53. /// <term></term>
  54. /// </item>
  55. /// <item>
  56. /// <term>Holding Bit Area</term>
  57. /// <term>H</term>
  58. /// <term>H100,H200</term>
  59. /// <term>10</term>
  60. /// <term>√</term>
  61. /// <term>√</term>
  62. /// <term></term>
  63. /// </item>
  64. /// <item>
  65. /// <term>Auxiliary Bit Area</term>
  66. /// <term>A</term>
  67. /// <term>A100,A200</term>
  68. /// <term>10</term>
  69. /// <term>√</term>
  70. /// <term>√</term>
  71. /// <term></term>
  72. /// </item>
  73. /// </list>
  74. /// </remarks>
  75. public class OmronHostLink : SerialDeviceBase<ReverseWordTransform>
  76. {
  77. #region Constructor
  78. /// <summary>
  79. /// 实例化一个默认的对象
  80. /// </summary>
  81. public OmronHostLink()
  82. {
  83. this.WordLength = 1;
  84. this.ByteTransform.DataFormat = DataFormat.CDAB;
  85. }
  86. #endregion
  87. #region Public Member
  88. /// <summary>
  89. /// Specifies whether or not there are network relays. Set “80” (ASCII: 38,30)
  90. /// when sending an FINS command to a CPU Unit on a network.Set “00” (ASCII: 30,30)
  91. /// when sending to a CPU Unit connected directly to the host computer.
  92. /// </summary>
  93. public byte ICF { get; set; } = 0x00;
  94. /// <summary>
  95. /// PLC的单元号地址
  96. /// </summary>
  97. /// <remarks>
  98. /// <note type="important">通常都为0</note>
  99. /// </remarks>
  100. public byte DA2 { get; set; } = 0x00;
  101. /// <summary>
  102. /// 上位机的单元号地址
  103. /// </summary>
  104. public byte SA2 { get; set; }
  105. /// <summary>
  106. /// 设备的标识号
  107. /// </summary>
  108. public byte SID { get; set; } = 0x00;
  109. /// <summary>
  110. /// The response wait time sets the time from when the CPU Unit receives a command block until it starts
  111. /// to return a response.It can be set from 0 to F in hexadecimal, in units of 10 ms.
  112. /// </summary>
  113. /// <example>
  114. /// If F(15) is set, the response will begin to be returned 150 ms (15 × 10 ms) after the command block was received.
  115. /// </example>
  116. public byte ResponseWaitTime { get; set; } = 0x30;
  117. /// <summary>
  118. /// PLC设备的站号信息
  119. /// </summary>
  120. public byte UnitNumber { get; set; }
  121. #endregion
  122. #region Read Write Support
  123. /// <summary>
  124. /// 批量读取PLC的数据,以字为单位,具体的地址参考文档
  125. /// </summary>
  126. /// <param name="address">地址信息</param>
  127. /// <param name="length">数据长度</param>
  128. /// <returns>读取结果信息</returns>
  129. public override OperateResult<byte[]> Read(string address, ushort length)
  130. {
  131. // 解析地址
  132. var command = OmronFinsNetHelper.BuildReadCommand(address, length, false);
  133. if (!command.IsSuccess) return command;
  134. // 核心交互
  135. OperateResult<byte[]> read = ReadBase(PackCommand(command.Content));
  136. if (!read.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(read);
  137. // 数据有效性分析
  138. OperateResult<byte[]> valid = ResponseValidAnalysis(read.Content, true);
  139. if (!valid.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(valid);
  140. // 读取到了正确的数据
  141. return OperateResult.CreateSuccessResult(valid.Content);
  142. }
  143. /// <summary>
  144. /// 批量写入PLC的数据,以字为单位,也就是说最少2个字节信息,具体的地址参考文档
  145. /// </summary>
  146. /// <param name="address">地址信息</param>
  147. /// <param name="value">数据值</param>
  148. /// <returns>是否写入成功</returns>
  149. public override OperateResult Write(string address, byte[] value)
  150. {
  151. // 获取指令
  152. var command = OmronFinsNetHelper.BuildWriteWordCommand(address, value, false); ;
  153. if (!command.IsSuccess) return command;
  154. // 核心数据交互
  155. OperateResult<byte[]> read = ReadBase(PackCommand(command.Content));
  156. if (!read.IsSuccess) return read;
  157. // 数据有效性分析
  158. OperateResult<byte[]> valid = ResponseValidAnalysis(read.Content, false);
  159. if (!valid.IsSuccess) return valid;
  160. // 成功
  161. return OperateResult.CreateSuccessResult();
  162. }
  163. #endregion
  164. #region Bool Read Write
  165. /// <summary>
  166. /// 从欧姆龙PLC中批量读取位软元件,返回读取结果
  167. /// </summary>
  168. /// <param name="address">读取地址,具体的地址参考文档</param>
  169. /// <param name="length">读取的长度</param>
  170. /// <returns>带成功标志的结果数据对象</returns>
  171. /// <example>
  172. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\OmronFinsNet.cs" region="ReadBool" title="ReadBool示例" />
  173. /// </example>
  174. public OperateResult<bool[]> ReadBool(string address, ushort length)
  175. {
  176. // 获取指令
  177. var command = OmronFinsNetHelper.BuildReadCommand(address, length, true);
  178. if (!command.IsSuccess) return OperateResult.CreateFailedResult<bool[]>(command);
  179. // 核心交互
  180. OperateResult<byte[]> read = ReadBase(PackCommand(command.Content));
  181. if (!read.IsSuccess) return OperateResult.CreateFailedResult<bool[]>(read);
  182. // 数据有效性分析
  183. OperateResult<byte[]> valid = ResponseValidAnalysis(read.Content, true);
  184. if (!valid.IsSuccess) return OperateResult.CreateFailedResult<bool[]>(valid);
  185. // 返回正确的数据信息
  186. return OperateResult.CreateSuccessResult(valid.Content.Select(m => m != 0x00 ? true : false).ToArray());
  187. }
  188. /// <summary>
  189. /// 从欧姆龙PLC中批量读取位软元件,返回读取结果
  190. /// </summary>
  191. /// <param name="address">读取地址,具体的地址参考文档</param>
  192. /// <returns>带成功标志的结果数据对象</returns>
  193. /// <remarks>
  194. /// 地址的格式请参照<see cref="ReadBool(string, ushort)"/>方法
  195. /// </remarks>
  196. /// <example>
  197. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\OmronFinsNet.cs" region="ReadBool" title="ReadBool示例" />
  198. /// </example>
  199. public OperateResult<bool> ReadBool(string address)
  200. {
  201. OperateResult<bool[]> read = ReadBool(address, 1);
  202. if (!read.IsSuccess) return OperateResult.CreateFailedResult<bool>(read);
  203. return OperateResult.CreateSuccessResult(read.Content[0]);
  204. }
  205. /// <summary>
  206. /// 向PLC中位软元件写入bool数组,返回值说明,比如你写入D100,values[0]对应D100.0
  207. /// </summary>
  208. /// <param name="address">要写入的数据地址,具体的地址参考文档</param>
  209. /// <param name="value">要写入的实际数据,长度为8的倍数</param>
  210. /// <returns>返回写入结果</returns>
  211. /// <example>
  212. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\OmronFinsNet.cs" region="WriteBool" title="WriteBool示例" />
  213. /// </example>
  214. public OperateResult Write(string address, bool value)
  215. {
  216. return Write(address, new bool[] { value });
  217. }
  218. /// <summary>
  219. /// 向PLC中位软元件写入bool数组,返回值说明,比如你写入D100,values[0]对应D100.0
  220. /// </summary>
  221. /// <param name="address">要写入的数据地址,具体的地址参考文档</param>
  222. /// <param name="values">要写入的实际数据,可以指定任意的长度</param>
  223. /// <returns>返回写入结果</returns>
  224. /// <example>
  225. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\OmronFinsNet.cs" region="WriteBool" title="WriteBool示例" />
  226. /// </example>
  227. public OperateResult Write(string address, bool[] values)
  228. {
  229. // 获取指令
  230. var command = OmronFinsNetHelper.BuildWriteWordCommand(address, values.Select(m => m ? (byte)0x01 : (byte)0x00).ToArray(), true); ;
  231. if (!command.IsSuccess) return command;
  232. // 核心数据交互
  233. OperateResult<byte[]> read = ReadBase(PackCommand(command.Content));
  234. if (!read.IsSuccess) return read;
  235. // 数据有效性分析
  236. OperateResult<byte[]> valid = ResponseValidAnalysis(read.Content, false);
  237. if (!valid.IsSuccess) return valid;
  238. // 成功
  239. return OperateResult.CreateSuccessResult();
  240. }
  241. #endregion
  242. #region Build Command
  243. /// <summary>
  244. /// 将普通的指令打包成完整的指令
  245. /// </summary>
  246. /// <param name="cmd">fins指令</param>
  247. /// <returns>完整的质量</returns>
  248. private byte[] PackCommand(byte[] cmd)
  249. {
  250. cmd = Encoding.ASCII.GetBytes(ByteToHexString(cmd, (char)0));
  251. byte[] buffer = new byte[18 + cmd.Length];
  252. buffer[0] = (byte)'@';
  253. buffer[1] = Melsec.MelsecHelper.BuildBytesFromData(this.UnitNumber)[0];
  254. buffer[2] = Melsec.MelsecHelper.BuildBytesFromData(this.UnitNumber)[1];
  255. buffer[3] = (byte)'F';
  256. buffer[4] = (byte)'A';
  257. buffer[5] = ResponseWaitTime;
  258. buffer[6] = Melsec.MelsecHelper.BuildBytesFromData(this.ICF)[0];
  259. buffer[7] = Melsec.MelsecHelper.BuildBytesFromData(this.ICF)[1];
  260. buffer[8] = Melsec.MelsecHelper.BuildBytesFromData(this.DA2)[0];
  261. buffer[9] = Melsec.MelsecHelper.BuildBytesFromData(this.DA2)[1];
  262. buffer[10] = Melsec.MelsecHelper.BuildBytesFromData(this.SA2)[0];
  263. buffer[11] = Melsec.MelsecHelper.BuildBytesFromData(this.SA2)[1];
  264. buffer[12] = Melsec.MelsecHelper.BuildBytesFromData(this.SID)[0];
  265. buffer[13] = Melsec.MelsecHelper.BuildBytesFromData(this.SID)[1];
  266. buffer[buffer.Length - 2] = (byte)'*';
  267. buffer[buffer.Length - 1] = 0x0D;
  268. cmd.CopyTo(buffer, 14);
  269. // 计算FCS
  270. int tmp = buffer[0];
  271. for (int i = 1; i < buffer.Length - 4; i++)
  272. {
  273. tmp = (tmp ^ buffer[i]);
  274. }
  275. buffer[buffer.Length - 4] = Melsec.MelsecHelper.BuildBytesFromData((byte)tmp)[0];
  276. buffer[buffer.Length - 3] = Melsec.MelsecHelper.BuildBytesFromData((byte)tmp)[1];
  277. string output = Encoding.ASCII.GetString(buffer);
  278. Console.WriteLine(output);
  279. return buffer;
  280. }
  281. /// <summary>
  282. /// 验证欧姆龙的Fins-TCP返回的数据是否正确的数据,如果正确的话,并返回所有的数据内容
  283. /// </summary>
  284. /// <param name="response">来自欧姆龙返回的数据内容</param>
  285. /// <param name="isRead">是否读取</param>
  286. /// <returns>带有是否成功的结果对象</returns>
  287. public static OperateResult<byte[]> ResponseValidAnalysis(byte[] response, bool isRead)
  288. {
  289. // 数据有效性分析
  290. if (response.Length >= 27)
  291. {
  292. // 提取错误码
  293. int err = Convert.ToInt32(Encoding.ASCII.GetString(response, 19, 4));
  294. byte[] Content = null;
  295. if (response.Length > 27)
  296. {
  297. Content =HexStringToBytes(Encoding.ASCII.GetString(response, 23, response.Length - 27));
  298. }
  299. if (err > 0) return new OperateResult<byte[]>()
  300. {
  301. ErrorCode = err,
  302. Content = Content
  303. };
  304. else
  305. {
  306. return OperateResult.CreateSuccessResult(Content);
  307. }
  308. }
  309. return new OperateResult<byte[]>(StringResources.Language.OmronReceiveDataError);
  310. }
  311. /// <summary>
  312. /// 字节数据转化成16进制表示的字符串 ->
  313. /// Byte data into a string of 16 binary representations
  314. /// </summary>
  315. /// <param name="InBytes">字节数组</param>
  316. /// <param name="segment">分割符</param>
  317. /// <returns>返回的字符串</returns>
  318. /// <exception cref="NullReferenceException"></exception>
  319. /// <example>
  320. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ByteToHexStringExample2" title="ByteToHexString示例" />
  321. /// </example>
  322. public static string ByteToHexString(byte[] InBytes, char segment)
  323. {
  324. StringBuilder sb = new StringBuilder();
  325. foreach (byte InByte in InBytes)
  326. {
  327. if (segment == 0) sb.Append(string.Format("{0:X2}", InByte));
  328. else sb.Append(string.Format("{0:X2}{1}", InByte, segment));
  329. }
  330. if (segment != 0 && sb.Length > 1 && sb[sb.Length - 1] == segment)
  331. {
  332. sb.Remove(sb.Length - 1, 1);
  333. }
  334. return sb.ToString();
  335. }
  336. /// <summary>
  337. /// 将16进制的字符串转化成Byte数据,将检测每2个字符转化,也就是说,中间可以是任意字符 ->
  338. /// Converts a 16-character string into byte data, which will detect every 2 characters converted, that is, the middle can be any character
  339. /// </summary>
  340. /// <param name="hex">十六进制的字符串,中间可以是任意的分隔符</param>
  341. /// <returns>转换后的字节数组</returns>
  342. /// <remarks>参数举例:AA 01 34 A8</remarks>
  343. /// <example>
  344. /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="HexStringToBytesExample" title="HexStringToBytes示例" />
  345. /// </example>
  346. public static byte[] HexStringToBytes(string hex)
  347. {
  348. hex = hex.ToUpper();
  349. List<char> hexCharList = new List<char>()
  350. {
  351. '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
  352. };
  353. MemoryStream ms = new MemoryStream();
  354. for (int i = 0; i < hex.Length; i++)
  355. {
  356. if ((i + 1) < hex.Length)
  357. {
  358. if (hexCharList.Contains(hex[i]) && hexCharList.Contains(hex[i + 1]))
  359. {
  360. // 这是一个合格的字节数据
  361. ms.WriteByte((byte)(hexCharList.IndexOf(hex[i]) * 16 + hexCharList.IndexOf(hex[i + 1])));
  362. i++;
  363. }
  364. }
  365. }
  366. byte[] result = ms.ToArray();
  367. ms.Dispose();
  368. return result;
  369. }
  370. #endregion
  371. }
  372. }