SCADA.cs 15 KB

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