SCADA.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. using OPC_Client;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Data;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using System.Timers;
  9. using System.Xml.Linq;
  10. namespace EBRDemo
  11. {
  12. class SCADA
  13. {
  14. private OPC_Client.Client client = new OPC_Client.Client();
  15. /// <summary>
  16. /// 第一个string代表唯一设备编码,第二个string代表批次信息
  17. /// </summary>
  18. private Dictionary<string, string> BatchNums = new Dictionary<string, string>();
  19. //每10s在屏幕显示一次值
  20. private static System.Timers.Timer timer = new System.Timers.Timer(10000);
  21. // private string _opcServerName = "Studio.Scada.OPC.5";//现场服务器名称
  22. private string _opcServerName = "Kepware.KEPServerEX.V6";//测试服务器名称
  23. /// <summary>
  24. /// 添加采集点位
  25. /// </summary>
  26. public void AddItems()
  27. {
  28. string path = @"采集点位.csv";//采集点位item,需要与实际一致
  29. DataTable table = ReadCSV(path);
  30. if (table.Columns.Count == 3)
  31. {
  32. foreach (DataRow row in table.Rows)
  33. {
  34. client.itemIDs.Add(row[0].ToString());
  35. client.descritions.Add(row[1].ToString());
  36. client.equipMentCodes.Add(row[2].ToString());
  37. }
  38. //建立连接
  39. client._opcServerName = _opcServerName;
  40. client.Connect();
  41. if (client.isConnected)
  42. {
  43. //MessageBox.Show("未连接成功");
  44. Console.WriteLine("连接成功");
  45. }
  46. else
  47. {
  48. Console.WriteLine("未连接成功");
  49. }
  50. //数据变化时
  51. client.Data_Change += Client_DataChange;
  52. //异步写入多个值的回调方法
  53. client.Async_WriteMuchComplete += AsyncWriteMuchComplete;
  54. timer.Elapsed += OnTimedEvent;
  55. timer.AutoReset = true;
  56. timer.Enabled = true;
  57. }
  58. }
  59. private void OnTimedEvent(Object source, ElapsedEventArgs e)
  60. {
  61. Console.WriteLine("每10s输出一次值");
  62. //WriteSingleValue("数据类型示例.8 位设备.S 寄存器.String4", "demossss");
  63. //WriteMuchValue();
  64. foreach (OPC_Client.Data data in client.dataCenter.Values)
  65. {
  66. Console.WriteLine(data.itemID + ":" + data.value.ToString());
  67. }
  68. }
  69. /// <summary>
  70. /// 写入值回调方法
  71. /// TransactionID,人为设置规定的标识ID
  72. /// </summary>
  73. /// <param name="TransactionID"></param>
  74. /// <param name="NumItems"></param>
  75. /// <param name="ClientHandles"></param>
  76. /// <param name="Errors"></param>
  77. private void AsyncWriteMuchComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors)
  78. {
  79. }
  80. /// <summary>
  81. /// NumItems 变化的数据数目
  82. /// ClientHandles clientID
  83. /// </summary>
  84. /// <param name="NumItems"></param>
  85. /// <param name="ClientHandles"></param>
  86. private void Client_DataChange(int NumItems, ref Array ClientHandles)
  87. {
  88. }
  89. #region 数据更改
  90. /// <summary>
  91. /// 刷新所有数据,不需要高频使用
  92. /// </summary>
  93. private void AsyncRefreshAll()
  94. {
  95. client.AsyncRefreshAll();
  96. }
  97. /// <summary>
  98. /// 更新批次号
  99. /// </summary>
  100. private void UpdateBatchNum()
  101. {
  102. foreach (Data data in client.dataCenter.Values)
  103. {
  104. string batchNum = "";
  105. //根据设备编码获取当前批次信息
  106. if (BatchNums.TryGetValue(data.equipMentCode, out batchNum))
  107. data.batchNum = batchNum;
  108. }
  109. }
  110. /// <summary>
  111. /// 写单个值
  112. /// </summary>
  113. private void WriteSingleValue(string item,object obj_value)
  114. {
  115. try
  116. {
  117. //根据item值,更新值
  118. //如果数据类型不对,可能导致错误
  119. client.WriteOneValue(item, obj_value);
  120. }
  121. catch(Exception err)
  122. {
  123. Console.WriteLine(err.Message);
  124. }
  125. }
  126. /// <summary>
  127. /// 写多个值
  128. /// </summary>
  129. private void WriteMuchValue()
  130. {
  131. try
  132. {
  133. List<string> itemIds = new List<string>();
  134. itemIds.Add("通道 1.设备 1.标记 1");
  135. itemIds.Add("通道 1.设备 1.标记 2");
  136. itemIds.Add("通道 1.设备 1.标记 3");
  137. itemIds.Add("数据类型示例.8 位设备.S 寄存器.String1");
  138. List<object> values = new List<object>();
  139. values.AddRange(new List<object> { 17, 18, 19, "20" });
  140. Array erros;
  141. int TransactionID = 8;
  142. int cancelID;
  143. //写入方法
  144. client.AsyncWriteMuchValue(itemIds.Count, itemIds, values, out erros, TransactionID, out cancelID);
  145. }
  146. catch
  147. {
  148. }
  149. }
  150. #endregion;
  151. #region 自定义方法,可以不看
  152. /// 给定文件的路径,读取文件的二进制数据,判断文件的编码类型
  153. /// <param name="FILE_NAME">文件路径</param>
  154. /// <returns>文件的编码类型</returns>
  155. public static System.Text.Encoding GetType(string FILE_NAME)
  156. {
  157. System.IO.FileStream fs = new System.IO.FileStream(FILE_NAME, System.IO.FileMode.Open,
  158. System.IO.FileAccess.Read);
  159. System.Text.Encoding r = GetType(fs);
  160. fs.Close();
  161. return r;
  162. }
  163. /// 通过给定的文件流,判断文件的编码类型
  164. /// <param name="fs">文件流</param>
  165. /// <returns>文件的编码类型</returns>
  166. public static System.Text.Encoding GetType(System.IO.FileStream fs)
  167. {
  168. byte[] Unicode = new byte[] { 0xFF, 0xFE, 0x41 };
  169. byte[] UnicodeBIG = new byte[] { 0xFE, 0xFF, 0x00 };
  170. byte[] UTF8 = new byte[] { 0xEF, 0xBB, 0xBF }; //带BOM
  171. System.Text.Encoding reVal = System.Text.Encoding.Default;
  172. System.IO.BinaryReader r = new System.IO.BinaryReader(fs, System.Text.Encoding.Default);
  173. int i;
  174. int.TryParse(fs.Length.ToString(), out i);
  175. byte[] ss = r.ReadBytes(i);
  176. if (IsUTF8Bytes(ss) || (ss[0] == 0xEF && ss[1] == 0xBB && ss[2] == 0xBF))
  177. {
  178. reVal = System.Text.Encoding.UTF8;
  179. }
  180. else if (ss[0] == 0xFE && ss[1] == 0xFF && ss[2] == 0x00)
  181. {
  182. reVal = System.Text.Encoding.BigEndianUnicode;
  183. }
  184. else if (ss[0] == 0xFF && ss[1] == 0xFE && ss[2] == 0x41)
  185. {
  186. reVal = System.Text.Encoding.Unicode;
  187. }
  188. r.Close();
  189. return reVal;
  190. }
  191. /// 判断是否是不带 BOM 的 UTF8 格式
  192. /// <param name="data"></param>
  193. /// <returns></returns>
  194. private static bool IsUTF8Bytes(byte[] data)
  195. {
  196. int charByteCounter = 1; //计算当前正分析的字符应还有的字节数
  197. byte curByte; //当前分析的字节.
  198. for (int i = 0; i < data.Length; i++)
  199. {
  200. curByte = data[i];
  201. if (charByteCounter == 1)
  202. {
  203. if (curByte >= 0x80)
  204. {
  205. //判断当前
  206. while (((curByte <<= 1) & 0x80) != 0)
  207. {
  208. charByteCounter++;
  209. }
  210. //标记位首位若为非0 则至少以2个1开始 如:110XXXXX...........1111110X 
  211. if (charByteCounter == 1 || charByteCounter > 6)
  212. {
  213. return false;
  214. }
  215. }
  216. }
  217. else
  218. {
  219. //若是UTF-8 此时第一位必须为1
  220. if ((curByte & 0xC0) != 0x80)
  221. {
  222. return false;
  223. }
  224. charByteCounter--;
  225. }
  226. }
  227. if (charByteCounter > 1)
  228. {
  229. throw new Exception("非预期的byte格式");
  230. }
  231. return true;
  232. }
  233. //导入csv文件,区分英文引号和英文逗号
  234. //返回字符串中特定字符个数
  235. private int GetNum_String_Special(string str1, string str2)
  236. {
  237. int i = 0;
  238. if (str1.Contains(str2))
  239. {
  240. string str3;
  241. str3 = str1.Replace(str2, "");
  242. i = (str1.Length - str3.Length) / str2.Length;
  243. }
  244. else
  245. {
  246. i = 0;
  247. }
  248. return i;
  249. }
  250. //判断一个数是奇数还是偶数,false代表偶数,true代表奇数
  251. //fdf
  252. private bool EorO(int i)
  253. {
  254. bool b = false;
  255. if (i % 2 == 1)
  256. {
  257. b = true;
  258. }
  259. else
  260. {
  261. b = false;
  262. }
  263. return b;
  264. }
  265. //修正csv文件中含有特殊字符(英文逗号,英文引号)
  266. //读取是按行进行的
  267. private List<string> Amend_str(string csv)
  268. {
  269. string newStr = "";
  270. string[] str_old = csv.Split(',');
  271. List<string> str_new = new List<string>();
  272. bool isSplice = false;//是否需要连接后面字符串
  273. foreach (string str in str_old)
  274. {
  275. int i = 0;
  276. i = GetNum_String_Special(str, "\"");
  277. //为奇数代表一定含有英文逗号
  278. //直到获取第一个奇数
  279. if (!isSplice)
  280. {
  281. //从0到1的跳变,代表英文逗号分隔符的起始第一个
  282. if (EorO(i))
  283. {
  284. isSplice = true;//表示需要与后面的字符串进行连接
  285. newStr += str + @",";
  286. }
  287. //为偶数,则代表不含英文逗号,可能含有英文引号
  288. else
  289. {
  290. //一个引号代替两个引号
  291. newStr = str.Replace("\"\"", "\"");
  292. str_new.Add(newStr);
  293. newStr = string.Empty;
  294. }
  295. }
  296. //表示后面的均为需要进行连接的字符串,如果后面的字符串中英文引号为单,则表示连接到此为止
  297. else
  298. {
  299. //奇数,则代表连接到此为止
  300. if (EorO(i))
  301. {
  302. newStr += str;
  303. newStr = newStr.Replace("\"\"", "\"");
  304. str_new.Add(newStr);
  305. newStr = string.Empty;
  306. isSplice = false;
  307. }
  308. //为偶数,则表示不是最后一个连接项
  309. else
  310. {
  311. newStr += str + @",";
  312. }
  313. }
  314. }
  315. return str_new;
  316. }
  317. //读取文件,返回datatable,head代表是否读取首行标题,head不可取,必须要
  318. //读取标题行,否则返回table容易出错,当CSV格式首航单元格不是最多的时候,易丢失数据
  319. private DataTable ReadCSV(string filePath)
  320. {
  321. DataTable dt = new DataTable();
  322. System.Text.Encoding encoding = GetType(filePath);
  323. System.IO.StreamReader sr = new System.IO.StreamReader(filePath, encoding); //记录每次读取的一行记录
  324. string strLine = "";
  325. //记录每行记录中的各字段内容
  326. string[] aryLine = null;
  327. string[] tableHead = null;
  328. //标示列数
  329. int columnCount = 0;
  330. //标示是否是读取的第一行
  331. bool IsFirst = true;
  332. //逐行读取CSV中的数据
  333. while ((strLine = sr.ReadLine()) != null)
  334. {
  335. if (IsFirst)
  336. {
  337. tableHead = Amend_str(strLine).ToArray();
  338. IsFirst = false;
  339. columnCount = tableHead.Length;
  340. //创建列
  341. for (int i = 0; i < columnCount; i++)
  342. {
  343. DataColumn dc = new DataColumn(tableHead[i]);
  344. dt.Columns.Add(dc);
  345. }
  346. }
  347. else
  348. {
  349. aryLine = Amend_str(strLine).ToArray();
  350. DataRow dr = dt.NewRow();
  351. for (int j = 0; j < columnCount; j++)
  352. {
  353. dr[j] = aryLine[j];
  354. }
  355. dt.Rows.Add(dr);
  356. }
  357. }
  358. sr.Close();
  359. return dt;
  360. }
  361. // private object obj_GetDataTable = new object();
  362. /// <summary>
  363. /// 连接数据库,获取table
  364. /// </summary>
  365. /// <param name="command"></param>
  366. /// <param name="conn"></param>
  367. /// <returns></returns>
  368. private DataTable GetDataTable(string command, string conn)
  369. {
  370. DataTable table = new DataTable();
  371. //连接SQL数据库
  372. System.Data.SqlClient.SqlConnection sqlConnection;
  373. System.Data.SqlClient.SqlCommand sqlCommand;
  374. try
  375. {
  376. //查询数据库SQL
  377. sqlConnection = new System.Data.SqlClient.SqlConnection(conn);
  378. sqlConnection.Open();
  379. sqlCommand = new System.Data.SqlClient.SqlCommand(command, sqlConnection);
  380. System.Data.SqlClient.SqlDataAdapter sda = new System.Data.SqlClient.SqlDataAdapter();
  381. sda.SelectCommand = sqlCommand;
  382. DataSet ds = new DataSet();
  383. sda.Fill(ds, "cs");
  384. table = ds.Tables[0];
  385. sqlConnection.Close();
  386. }
  387. catch (Exception err)
  388. {
  389. // MessageBox.Show(err.Message);
  390. Console.WriteLine(DateTime.Now.ToString() + ":" + err.Message);
  391. }
  392. return table;
  393. }
  394. #endregion
  395. }
  396. }